Merge branch 'master' into backend_plugins
* master: changelog: adds note about closing #10131 Explicitly specify default region in CloudWatch datasource (#9440) wait for all sub routines to finish changelog: adds ntoe about closing #10111 postgres: change $__timeGroup macro to include "AS time" column alias (#10119) fixes broken test Solves problem with Github authentication restriction by organization membership when the organization's access policy is set to "Access restricted". "Access restricted" policy should not stop user to authenticate.
This commit is contained in:
@@ -33,9 +33,14 @@ From `/etc/grafana/datasources` to `/etc/grafana/provisioning/datasources` when
|
|||||||
* **Dashboard**: Make it possible to start dashboards from search and dashboard list panel [#1871](https://github.com/grafana/grafana/issues/1871)
|
* **Dashboard**: Make it possible to start dashboards from search and dashboard list panel [#1871](https://github.com/grafana/grafana/issues/1871)
|
||||||
* **Annotations**: Posting annotations now return the id of the annotation [#9798](https://github.com/grafana/grafana/issues/9798)
|
* **Annotations**: Posting annotations now return the id of the annotation [#9798](https://github.com/grafana/grafana/issues/9798)
|
||||||
* **Systemd**: Use systemd notification ready flag [#10024](https://github.com/grafana/grafana/issues/10024), thx [@jgrassler](https://github.com/jgrassler)
|
* **Systemd**: Use systemd notification ready flag [#10024](https://github.com/grafana/grafana/issues/10024), thx [@jgrassler](https://github.com/jgrassler)
|
||||||
|
* **Github**: Use organizations_url provided from github to verify user belongs in org. [#10111](https://github.com/grafana/grafana/issues/10111), thx
|
||||||
|
[@adiletmaratov](https://github.com/adiletmaratov)
|
||||||
|
* **Backend**: Fixed bug where Grafana exited before all sub routines where finished [#10131](https://github.com/grafana/grafana/issues/10131)
|
||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
* **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
|
* **RabbitMq**: Remove support for publishing events to RabbitMQ [#9645](https://github.com/grafana/grafana/issues/9645)
|
||||||
|
|
||||||
|
|
||||||
## Fixes
|
## Fixes
|
||||||
* **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
|
* **Sensu**: Send alert message to sensu output [#9551](https://github.com/grafana/grafana/issues/9551), thx [@cjchand](https://github.com/cjchand)
|
||||||
* **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
* **Singlestat**: suppress error when result contains no datapoints [#9636](https://github.com/grafana/grafana/issues/9636), thx [@utkarshcmu](https://github.com/utkarshcmu)
|
||||||
|
|||||||
@@ -78,11 +78,14 @@ CloudWatch Datasource Plugin provides the following queries you can specify in t
|
|||||||
edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
|
edit view. They allow you to fill a variable's options list with things like `region`, `namespaces`, `metric names`
|
||||||
and `dimension keys/values`.
|
and `dimension keys/values`.
|
||||||
|
|
||||||
|
In place of `region` you can specify `default` to use the default region configured in the datasource for the query,
|
||||||
|
e.g. `metrics(AWS/DynamoDB, default)` or `dimension_values(default, ..., ..., ...)`.
|
||||||
|
|
||||||
Name | Description
|
Name | Description
|
||||||
------- | --------
|
------- | --------
|
||||||
*regions()* | Returns a list of regions AWS provides their service.
|
*regions()* | Returns a list of regions AWS provides their service.
|
||||||
*namespaces()* | Returns a list of namespaces CloudWatch support.
|
*namespaces()* | Returns a list of namespaces CloudWatch support.
|
||||||
*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region for custom metrics)
|
*metrics(namespace, [region])* | Returns a list of metrics in the namespace. (specify region or use "default" for custom metrics)
|
||||||
*dimension_keys(namespace)* | Returns a list of dimension keys in the namespace.
|
*dimension_keys(namespace)* | Returns a list of dimension keys in the namespace.
|
||||||
*dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
|
*dimension_values(region, namespace, metric, dimension_key)* | Returns a list of dimension values matching the specified `region`, `namespace`, `metric` and `dimension_key`.
|
||||||
*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
|
*ebs_volume_ids(region, instance_id)* | Returns a list of volume ids matching the specified `region`, `instance_id`.
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Macro example | Description
|
|||||||
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *extract(epoch from dateColumn) BETWEEN 1494410783 AND 1494497183*
|
*$__timeFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name. For example, *extract(epoch from dateColumn) BETWEEN 1494410783 AND 1494497183*
|
||||||
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
|
*$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *to_timestamp(1494410783)*
|
||||||
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
|
*$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *to_timestamp(1494497183)*
|
||||||
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from "dateColumn")/300)::bigint*300*
|
*$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300 AS time*
|
||||||
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
*$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183*
|
||||||
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
|
*$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783*
|
||||||
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
|
*$__unixEpochTo()* | Will be replaced by the end of the currently active time selection as unix timestamp. For example, *1494497183*
|
||||||
@@ -94,7 +94,7 @@ Example with `metric` column
|
|||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
$__timeGroup(time_date_time,'5m') as time,
|
$__timeGroup(time_date_time,'5m'),
|
||||||
min(value_double),
|
min(value_double),
|
||||||
'min' as metric
|
'min' as metric
|
||||||
FROM test_data
|
FROM test_data
|
||||||
@@ -107,7 +107,7 @@ Example with multiple columns:
|
|||||||
|
|
||||||
```sql
|
```sql
|
||||||
SELECT
|
SELECT
|
||||||
$__timeGroup(time_date_time,'5m') as time,
|
$__timeGroup(time_date_time,'5m'),
|
||||||
min(value_double) as min_value,
|
min(value_double) as min_value,
|
||||||
max(value_double) as max_value
|
max(value_double) as max_value
|
||||||
FROM test_data
|
FROM test_data
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (hs *HttpServer) Start(ctx context.Context) error {
|
|||||||
|
|
||||||
func (hs *HttpServer) Shutdown(ctx context.Context) error {
|
func (hs *HttpServer) Shutdown(ctx context.Context) error {
|
||||||
err := hs.httpSrv.Shutdown(ctx)
|
err := hs.httpSrv.Shutdown(ctx)
|
||||||
hs.log.Info("stopped http server")
|
hs.log.Info("Stopped HTTP server")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
|
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
|
||||||
@@ -40,9 +40,6 @@ var homePath = flag.String("homepath", "", "path to grafana install/home path, d
|
|||||||
var pidFile = flag.String("pidfile", "", "path to pid file")
|
var pidFile = flag.String("pidfile", "", "path to pid file")
|
||||||
var exitChan = make(chan int)
|
var exitChan = make(chan int)
|
||||||
|
|
||||||
func init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
v := flag.Bool("v", false, "prints current version and exits")
|
v := flag.Bool("v", false, "prints current version and exits")
|
||||||
profile := flag.Bool("profile", false, "Turn on pprof profiling")
|
profile := flag.Bool("profile", false, "Turn on pprof profiling")
|
||||||
@@ -82,12 +79,28 @@ func main() {
|
|||||||
setting.BuildStamp = buildstampInt64
|
setting.BuildStamp = buildstampInt64
|
||||||
|
|
||||||
metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
|
metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
|
||||||
|
shutdownCompleted := make(chan int)
|
||||||
server := NewGrafanaServer()
|
server := NewGrafanaServer()
|
||||||
server.Start()
|
|
||||||
|
go listenToSystemSignals(server, shutdownCompleted)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
code := 0
|
||||||
|
if err := server.Start(); err != nil {
|
||||||
|
log.Error2("Startup failed", "error", err)
|
||||||
|
code = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exitChan <- code
|
||||||
|
}()
|
||||||
|
|
||||||
|
code := <-shutdownCompleted
|
||||||
|
log.Info2("Grafana shutdown completed.", "code", code)
|
||||||
|
log.Close()
|
||||||
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listenToSystemSignals(server models.GrafanaServer) {
|
func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
|
||||||
signalChan := make(chan os.Signal, 1)
|
signalChan := make(chan os.Signal, 1)
|
||||||
ignoreChan := make(chan os.Signal, 1)
|
ignoreChan := make(chan os.Signal, 1)
|
||||||
code := 0
|
code := 0
|
||||||
@@ -97,10 +110,12 @@ func listenToSystemSignals(server models.GrafanaServer) {
|
|||||||
|
|
||||||
select {
|
select {
|
||||||
case sig := <-signalChan:
|
case sig := <-signalChan:
|
||||||
// Stops trace if profiling has been enabled
|
trace.Stop() // Stops trace if profiling has been enabled
|
||||||
trace.Stop()
|
|
||||||
server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
|
server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
|
||||||
|
shutdownCompleted <- 0
|
||||||
case code = <-exitChan:
|
case code = <-exitChan:
|
||||||
|
trace.Stop() // Stops trace if profiling has been enabled
|
||||||
server.Shutdown(code, "startup error")
|
server.Shutdown(code, "startup error")
|
||||||
|
shutdownCompleted <- code
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
|
||||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -20,7 +19,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/log"
|
"github.com/grafana/grafana/pkg/log"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
"github.com/grafana/grafana/pkg/login"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/models"
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/services/alerting"
|
"github.com/grafana/grafana/pkg/services/alerting"
|
||||||
"github.com/grafana/grafana/pkg/services/cleanup"
|
"github.com/grafana/grafana/pkg/services/cleanup"
|
||||||
@@ -33,7 +31,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/tracing"
|
"github.com/grafana/grafana/pkg/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewGrafanaServer() models.GrafanaServer {
|
func NewGrafanaServer() *GrafanaServerImpl {
|
||||||
rootCtx, shutdownFn := context.WithCancel(context.Background())
|
rootCtx, shutdownFn := context.WithCancel(context.Background())
|
||||||
childRoutines, childCtx := errgroup.WithContext(rootCtx)
|
childRoutines, childCtx := errgroup.WithContext(rootCtx)
|
||||||
|
|
||||||
@@ -54,9 +52,7 @@ type GrafanaServerImpl struct {
|
|||||||
httpServer *api.HttpServer
|
httpServer *api.HttpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GrafanaServerImpl) Start() {
|
func (g *GrafanaServerImpl) Start() error {
|
||||||
go listenToSystemSignals(g)
|
|
||||||
|
|
||||||
g.initLogging()
|
g.initLogging()
|
||||||
g.writePIDFile()
|
g.writePIDFile()
|
||||||
|
|
||||||
@@ -75,16 +71,12 @@ func (g *GrafanaServerImpl) Start() {
|
|||||||
defer pluginCloser()
|
defer pluginCloser()
|
||||||
|
|
||||||
if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
|
if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
|
||||||
logger.Error("Failed to provision Grafana from config", "error", err)
|
return fmt.Errorf("Failed to provision Grafana from config. error: %v", err)
|
||||||
g.Shutdown(1, "Startup failed")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tracingCloser, err := tracing.Init(setting.Cfg)
|
tracingCloser, err := tracing.Init(setting.Cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.log.Error("Tracing settings is not valid", "error", err)
|
return fmt.Errorf("Tracing settings is not valid. error: %v", err)
|
||||||
g.Shutdown(1, "Startup failed")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer tracingCloser.Close()
|
defer tracingCloser.Close()
|
||||||
|
|
||||||
@@ -99,13 +91,12 @@ func (g *GrafanaServerImpl) Start() {
|
|||||||
g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
|
g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
|
||||||
|
|
||||||
if err = notifications.Init(); err != nil {
|
if err = notifications.Init(); err != nil {
|
||||||
g.log.Error("Notification service failed to initialize", "error", err)
|
return fmt.Errorf("Notification service failed to initialize. error: %v", err)
|
||||||
g.Shutdown(1, "Startup failed")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SendSystemdNotification("READY=1")
|
sendSystemdNotification("READY=1")
|
||||||
g.startHttpServer()
|
|
||||||
|
return g.startHttpServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initSql() {
|
func initSql() {
|
||||||
@@ -129,16 +120,16 @@ func (g *GrafanaServerImpl) initLogging() {
|
|||||||
setting.LogConfigurationInfo()
|
setting.LogConfigurationInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GrafanaServerImpl) startHttpServer() {
|
func (g *GrafanaServerImpl) startHttpServer() error {
|
||||||
g.httpServer = api.NewHttpServer()
|
g.httpServer = api.NewHttpServer()
|
||||||
|
|
||||||
err := g.httpServer.Start(g.context)
|
err := g.httpServer.Start(g.context)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.log.Error("Fail to start server", "error", err)
|
return fmt.Errorf("Fail to start server. error: %v", err)
|
||||||
g.Shutdown(1, "Startup failed")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
|
func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
|
||||||
@@ -151,10 +142,9 @@ func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
|
|||||||
|
|
||||||
g.shutdownFn()
|
g.shutdownFn()
|
||||||
err = g.childRoutines.Wait()
|
err = g.childRoutines.Wait()
|
||||||
|
if err != nil && err != context.Canceled {
|
||||||
g.log.Info("Shutdown completed", "reason", err)
|
g.log.Error("Server shutdown completed with an error", "error", err)
|
||||||
log.Close()
|
}
|
||||||
os.Exit(code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GrafanaServerImpl) writePIDFile() {
|
func (g *GrafanaServerImpl) writePIDFile() {
|
||||||
@@ -179,7 +169,7 @@ func (g *GrafanaServerImpl) writePIDFile() {
|
|||||||
g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
|
g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendSystemdNotification(state string) error {
|
func sendSystemdNotification(state string) error {
|
||||||
notifySocket := os.Getenv("NOTIFY_SOCKET")
|
notifySocket := os.Getenv("NOTIFY_SOCKET")
|
||||||
|
|
||||||
if notifySocket == "" {
|
if notifySocket == "" {
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type GrafanaServer interface {
|
|
||||||
Start()
|
|
||||||
Shutdown(code int, reason string)
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
|
simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
|
||||||
brokenConfigs string = "./test-configs/borken-configs"
|
brokenConfigs string = "./test-configs/broken-configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDashboardsAsConfig(t *testing.T) {
|
func TestDashboardsAsConfig(t *testing.T) {
|
||||||
|
|||||||
@@ -58,12 +58,12 @@ func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
|
func (s *SocialGithub) IsOrganizationMember(client *http.Client, organizationsUrl string) bool {
|
||||||
if len(s.allowedOrganizations) == 0 {
|
if len(s.allowedOrganizations) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
organizations, err := s.FetchOrganizations(client)
|
organizations, err := s.FetchOrganizations(client, organizationsUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -167,12 +167,12 @@ func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
|
func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl string) ([]string, error) {
|
||||||
type Record struct {
|
type Record struct {
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs"))
|
response, err := HttpGet(client, organizationsUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error getting organizations: %s", err)
|
return nil, fmt.Errorf("Error getting organizations: %s", err)
|
||||||
}
|
}
|
||||||
@@ -193,10 +193,12 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
OrganizationsUrl string `json:"organizations_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := HttpGet(client, s.apiUrl)
|
response, err := HttpGet(client, s.apiUrl)
|
||||||
@@ -219,7 +221,7 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
|
|||||||
return nil, ErrMissingTeamMembership
|
return nil, ErrMissingTeamMembership
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.IsOrganizationMember(client) {
|
if !s.IsOrganizationMember(client, data.OrganizationsUrl) {
|
||||||
return nil, ErrMissingOrganizationMembership
|
return nil, ErrMissingOrganizationMembership
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
|
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
|
||||||
|
defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString()
|
||||||
|
if region == "default" {
|
||||||
|
region = defaultRegion
|
||||||
|
}
|
||||||
|
|
||||||
authType := e.DataSource.JsonData.Get("authType").MustString()
|
authType := e.DataSource.JsonData.Get("authType").MustString()
|
||||||
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
|
||||||
accessKey := ""
|
accessKey := ""
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error parsing interval %v", args[1])
|
return "", fmt.Errorf("error parsing interval %v", args[1])
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("(extract(epoch from \"%s\")/%v)::bigint*%v", args[0], interval.Seconds(), interval.Seconds()), nil
|
return fmt.Sprintf("(extract(epoch from %s)/%v)::bigint*%v AS time", args[0], interval.Seconds(), interval.Seconds()), nil
|
||||||
case "__unixEpochFilter":
|
case "__unixEpochFilter":
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
return "", fmt.Errorf("missing time column argument for macro %v", name)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
|
|||||||
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
|
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/300)::bigint*300")
|
So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time")
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("interpolate __timeTo function", func() {
|
Convey("interpolate __timeTo function", func() {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
!!item.metricName &&
|
!!item.metricName &&
|
||||||
!_.isEmpty(item.statistics);
|
!_.isEmpty(item.statistics);
|
||||||
}).map(function (item) {
|
}).map(function (item) {
|
||||||
item.region = templateSrv.replace(item.region, options.scopedVars);
|
item.region = templateSrv.replace(self.getActualRegion(item.region), options.scopedVars);
|
||||||
item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
|
item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
|
||||||
item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
|
item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
|
||||||
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
|
item.dimensions = self.convertDimensionFormat(item.dimensions, options.scopeVars);
|
||||||
@@ -165,21 +165,21 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
|
|
||||||
this.getMetrics = function (namespace, region) {
|
this.getMetrics = function (namespace, region) {
|
||||||
return this.doMetricQueryRequest('metrics', {
|
return this.doMetricQueryRequest('metrics', {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: templateSrv.replace(namespace)
|
namespace: templateSrv.replace(namespace)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDimensionKeys = function(namespace, region) {
|
this.getDimensionKeys = function(namespace, region) {
|
||||||
return this.doMetricQueryRequest('dimension_keys', {
|
return this.doMetricQueryRequest('dimension_keys', {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: templateSrv.replace(namespace)
|
namespace: templateSrv.replace(namespace)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
|
this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
|
||||||
return this.doMetricQueryRequest('dimension_values', {
|
return this.doMetricQueryRequest('dimension_values', {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: templateSrv.replace(namespace),
|
namespace: templateSrv.replace(namespace),
|
||||||
metricName: templateSrv.replace(metricName),
|
metricName: templateSrv.replace(metricName),
|
||||||
dimensionKey: templateSrv.replace(dimensionKey),
|
dimensionKey: templateSrv.replace(dimensionKey),
|
||||||
@@ -189,14 +189,14 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
|
|
||||||
this.getEbsVolumeIds = function(region, instanceId) {
|
this.getEbsVolumeIds = function(region, instanceId) {
|
||||||
return this.doMetricQueryRequest('ebs_volume_ids', {
|
return this.doMetricQueryRequest('ebs_volume_ids', {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(this.getActualRegion(region)),
|
||||||
instanceId: templateSrv.replace(instanceId)
|
instanceId: templateSrv.replace(instanceId)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getEc2InstanceAttribute = function(region, attributeName, filters) {
|
this.getEc2InstanceAttribute = function(region, attributeName, filters) {
|
||||||
return this.doMetricQueryRequest('ec2_instance_attribute', {
|
return this.doMetricQueryRequest('ec2_instance_attribute', {
|
||||||
region: templateSrv.replace(region),
|
region: templateSrv.replace(this.getActualRegion(region)),
|
||||||
attributeName: templateSrv.replace(attributeName),
|
attributeName: templateSrv.replace(attributeName),
|
||||||
filters: filters
|
filters: filters
|
||||||
});
|
});
|
||||||
@@ -267,7 +267,7 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
period = parseInt(period, 10);
|
period = parseInt(period, 10);
|
||||||
var parameters = {
|
var parameters = {
|
||||||
prefixMatching: annotation.prefixMatching,
|
prefixMatching: annotation.prefixMatching,
|
||||||
region: templateSrv.replace(annotation.region),
|
region: templateSrv.replace(this.getActualRegion(annotation.region)),
|
||||||
namespace: templateSrv.replace(annotation.namespace),
|
namespace: templateSrv.replace(annotation.namespace),
|
||||||
metricName: templateSrv.replace(annotation.metricName),
|
metricName: templateSrv.replace(annotation.metricName),
|
||||||
dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
|
dimensions: this.convertDimensionFormat(annotation.dimensions, {}),
|
||||||
@@ -341,6 +341,13 @@ function (angular, _, moment, dateMath, kbn, templatingVariable) {
|
|||||||
return this.defaultRegion;
|
return this.defaultRegion;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getActualRegion = function(region) {
|
||||||
|
if (region === 'default' || _.isEmpty(region)) {
|
||||||
|
return this.getDefaultRegion();
|
||||||
|
}
|
||||||
|
return region;
|
||||||
|
};
|
||||||
|
|
||||||
this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
|
this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
|
||||||
/* if the all checkbox is marked we should add all values to the targets */
|
/* if the all checkbox is marked we should add all values to the targets */
|
||||||
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
|
var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
target.statistics = target.statistics || ['Average'];
|
target.statistics = target.statistics || ['Average'];
|
||||||
target.dimensions = target.dimensions || {};
|
target.dimensions = target.dimensions || {};
|
||||||
target.period = target.period || '';
|
target.period = target.period || '';
|
||||||
target.region = target.region || '';
|
target.region = target.region || 'default';
|
||||||
|
|
||||||
$scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
|
$scope.regionSegment = uiSegmentSrv.getSegmentForValue($scope.target.region, 'select region');
|
||||||
$scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
|
$scope.namespaceSegment = uiSegmentSrv.getSegmentForValue($scope.target.namespace, 'select namespace');
|
||||||
@@ -51,7 +51,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
$scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
|
$scope.removeStatSegment = uiSegmentSrv.newSegment({fake: true, value: '-- remove stat --'});
|
||||||
|
|
||||||
if (_.isEmpty($scope.target.region)) {
|
if (_.isEmpty($scope.target.region)) {
|
||||||
$scope.target.region = $scope.datasource.getDefaultRegion();
|
$scope.target.region = 'default';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$scope.onChange) {
|
if (!$scope.onChange) {
|
||||||
@@ -148,6 +148,10 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
|
|
||||||
$scope.getRegions = function() {
|
$scope.getRegions = function() {
|
||||||
return $scope.datasource.metricFindQuery('regions()')
|
return $scope.datasource.metricFindQuery('regions()')
|
||||||
|
.then(function(results) {
|
||||||
|
results.unshift({ text: 'default'});
|
||||||
|
return results;
|
||||||
|
})
|
||||||
.then($scope.transformToSegments(true));
|
.then($scope.transformToSegments(true));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,55 @@ describe('CloudWatchDatasource', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('When query region is "default"', function () {
|
||||||
|
it('should return the datasource region if empty or "default"', function() {
|
||||||
|
var defaultRegion = instanceSettings.jsonData.defaultRegion;
|
||||||
|
|
||||||
|
expect(ctx.ds.getActualRegion()).to.be(defaultRegion);
|
||||||
|
expect(ctx.ds.getActualRegion('')).to.be(defaultRegion);
|
||||||
|
expect(ctx.ds.getActualRegion("default")).to.be(defaultRegion);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the specified region if specified', function() {
|
||||||
|
expect(ctx.ds.getActualRegion('some-fake-region-1')).to.be('some-fake-region-1');
|
||||||
|
});
|
||||||
|
|
||||||
|
var requestParams;
|
||||||
|
beforeEach(function() {
|
||||||
|
ctx.ds.performTimeSeriesQuery = function(request) {
|
||||||
|
requestParams = request;
|
||||||
|
return ctx.$q.when({data: {}});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should query for the datasource region if empty or "default"', function(done) {
|
||||||
|
var query = {
|
||||||
|
range: { from: 'now-1h', to: 'now' },
|
||||||
|
rangeRaw: { from: 1483228800, to: 1483232400 },
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
region: 'default',
|
||||||
|
namespace: 'AWS/EC2',
|
||||||
|
metricName: 'CPUUtilization',
|
||||||
|
dimensions: {
|
||||||
|
InstanceId: 'i-12345678'
|
||||||
|
},
|
||||||
|
statistics: ['Average'],
|
||||||
|
period: 300
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.ds.query(query).then(function(result) {
|
||||||
|
expect(requestParams.queries[0].region).to.be(instanceSettings.jsonData.defaultRegion);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
ctx.$rootScope.$apply();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('When performing CloudWatch query for extended statistics', function() {
|
describe('When performing CloudWatch query for extended statistics', function() {
|
||||||
var requestParams;
|
var requestParams;
|
||||||
|
|
||||||
@@ -348,6 +397,26 @@ describe('CloudWatchDatasource', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
|
||||||
|
scenario.setup(() => {
|
||||||
|
scenario.requestResponse = {
|
||||||
|
results: {
|
||||||
|
metricFindQuery: {
|
||||||
|
tables: [
|
||||||
|
{ rows: [['i-12345678', 'i-12345678']] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call __ListMetrics and return result', () => {
|
||||||
|
expect(scenario.result[0].text).to.contain('i-12345678');
|
||||||
|
expect(scenario.request.queries[0].type).to.be('metricFindQuery');
|
||||||
|
expect(scenario.request.queries[0].subtype).to.be('dimension_values');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should caclculate the correct period', function () {
|
it('should caclculate the correct period', function () {
|
||||||
var hourSec = 60 * 60;
|
var hourSec = 60 * 60;
|
||||||
var daySec = hourSec * 24;
|
var daySec = hourSec * 24;
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ Macros:
|
|||||||
- $__timeEpoch -> extract(epoch from column) as "time"
|
- $__timeEpoch -> extract(epoch from column) as "time"
|
||||||
- $__timeFilter(column) -> extract(epoch from column) BETWEEN 1492750877 AND 1492750877
|
- $__timeFilter(column) -> extract(epoch from column) BETWEEN 1492750877 AND 1492750877
|
||||||
- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
|
- $__unixEpochFilter(column) -> column > 1492750877 AND column < 1492750877
|
||||||
- $__timeGroup(column,'5m') -> (extract(epoch from "dateColumn")/300)::bigint*300
|
- $__timeGroup(column,'5m') -> (extract(epoch from column)/300)::bigint*300 AS time
|
||||||
|
|
||||||
Example of group by and order by with $__timeGroup:
|
Example of group by and order by with $__timeGroup:
|
||||||
SELECT
|
SELECT
|
||||||
$__timeGroup(date_time_col, '1h') AS time,
|
$__timeGroup(date_time_col, '1h'),
|
||||||
sum(value) as value
|
sum(value) as value
|
||||||
FROM yourtable
|
FROM yourtable
|
||||||
GROUP BY time
|
GROUP BY time
|
||||||
|
|||||||
Reference in New Issue
Block a user