Chore: Add CloudWatch HTTP API tests (#29691)
* CloudWatch: Add HTTP API tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# API integration tests
|
||||
|
||||
This directory contains Grafana HTTP API integration tests.
|
||||
@@ -0,0 +1,360 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
|
||||
"github.com/aws/aws-sdk-go/service/cloudwatchlogs/cloudwatchlogsiface"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/server"
|
||||
"github.com/grafana/grafana/pkg/tsdb"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch"
|
||||
|
||||
cwapi "github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func TestQueryCloudWatchMetrics(t *testing.T) {
|
||||
grafDir, cfgPath := createGrafDir(t)
|
||||
sqlStore := setUpDatabase(t, grafDir)
|
||||
addr := startGrafana(t, grafDir, cfgPath, sqlStore)
|
||||
|
||||
origNewCWClient := cloudwatch.NewCWClient
|
||||
t.Cleanup(func() {
|
||||
cloudwatch.NewCWClient = origNewCWClient
|
||||
})
|
||||
var client cloudwatch.FakeCWClient
|
||||
cloudwatch.NewCWClient = func(sess *session.Session) cloudwatchiface.CloudWatchAPI {
|
||||
return client
|
||||
}
|
||||
|
||||
t.Run("Custom metrics", func(t *testing.T) {
|
||||
client = cloudwatch.FakeCWClient{
|
||||
Metrics: []*cwapi.Metric{
|
||||
{
|
||||
MetricName: aws.String("Test_MetricName"),
|
||||
Dimensions: []*cwapi.Dimension{
|
||||
{
|
||||
Name: aws.String("Test_DimensionName"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
req := dtos.MetricRequest{
|
||||
Queries: []*simplejson.Json{
|
||||
simplejson.NewFromAny(map[string]interface{}{
|
||||
"type": "metricFindQuery",
|
||||
"subtype": "metrics",
|
||||
"region": "us-east-1",
|
||||
"namespace": "custom",
|
||||
"datasourceId": 1,
|
||||
}),
|
||||
},
|
||||
}
|
||||
tr := makeCWRequest(t, req, addr)
|
||||
|
||||
assert.Equal(t, tsdb.Response{
|
||||
Results: map[string]*tsdb.QueryResult{
|
||||
"A": {
|
||||
RefId: "A",
|
||||
Meta: simplejson.NewFromAny(map[string]interface{}{
|
||||
"rowCount": float64(1),
|
||||
}),
|
||||
Tables: []*tsdb.Table{
|
||||
{
|
||||
Columns: []tsdb.TableColumn{
|
||||
{
|
||||
Text: "text",
|
||||
},
|
||||
{
|
||||
Text: "value",
|
||||
},
|
||||
},
|
||||
Rows: []tsdb.RowValues{
|
||||
{
|
||||
"Test_MetricName",
|
||||
"Test_MetricName",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, tr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestQueryCloudWatchLogs(t *testing.T) {
|
||||
grafDir, cfgPath := createGrafDir(t)
|
||||
sqlStore := setUpDatabase(t, grafDir)
|
||||
addr := startGrafana(t, grafDir, cfgPath, sqlStore)
|
||||
|
||||
origNewCWLogsClient := cloudwatch.NewCWLogsClient
|
||||
t.Cleanup(func() {
|
||||
cloudwatch.NewCWLogsClient = origNewCWLogsClient
|
||||
})
|
||||
|
||||
var client cloudwatch.FakeCWLogsClient
|
||||
cloudwatch.NewCWLogsClient = func(sess *session.Session) cloudwatchlogsiface.CloudWatchLogsAPI {
|
||||
return client
|
||||
}
|
||||
|
||||
t.Run("Describe log groups", func(t *testing.T) {
|
||||
client = cloudwatch.FakeCWLogsClient{}
|
||||
|
||||
req := dtos.MetricRequest{
|
||||
Queries: []*simplejson.Json{
|
||||
simplejson.NewFromAny(map[string]interface{}{
|
||||
"type": "logAction",
|
||||
"subtype": "DescribeLogGroups",
|
||||
"region": "us-east-1",
|
||||
"datasourceId": 1,
|
||||
}),
|
||||
},
|
||||
}
|
||||
tr := makeCWRequest(t, req, addr)
|
||||
|
||||
dataFrames := tsdb.NewDecodedDataFrames(data.Frames{
|
||||
&data.Frame{
|
||||
Name: "logGroups",
|
||||
Fields: []*data.Field{
|
||||
data.NewField("logGroupName", nil, []*string{}),
|
||||
},
|
||||
Meta: &data.FrameMeta{
|
||||
PreferredVisualization: "logs",
|
||||
},
|
||||
},
|
||||
})
|
||||
// Have to call this so that dataFrames.encoded is non-nil, for the comparison
|
||||
// In the future we should use gocmp instead and ignore this field
|
||||
_, err := dataFrames.Encoded()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tsdb.Response{
|
||||
Results: map[string]*tsdb.QueryResult{
|
||||
"A": {
|
||||
RefId: "A",
|
||||
Dataframes: dataFrames,
|
||||
},
|
||||
},
|
||||
}, tr)
|
||||
})
|
||||
}
|
||||
|
||||
func makeCWRequest(t *testing.T, req dtos.MetricRequest, addr string) tsdb.Response {
|
||||
t.Helper()
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
err := enc.Encode(&req)
|
||||
require.NoError(t, err)
|
||||
u := fmt.Sprintf("http://%s/api/ds/query", addr)
|
||||
t.Logf("Making POST request to %s", u)
|
||||
// nolint:gosec
|
||||
resp, err := http.Post(u, "application/json", &buf)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
buf = bytes.Buffer{}
|
||||
_, err = io.Copy(&buf, resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
var tr tsdb.Response
|
||||
err = json.Unmarshal(buf.Bytes(), &tr)
|
||||
require.NoError(t, err)
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
func createGrafDir(t *testing.T) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll(tmpDir)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
rootDir := filepath.Join("..", "..", "..", "..")
|
||||
|
||||
cfgDir := filepath.Join(tmpDir, "conf")
|
||||
err = os.MkdirAll(cfgDir, 0750)
|
||||
require.NoError(t, err)
|
||||
dataDir := filepath.Join(tmpDir, "data")
|
||||
// nolint:gosec
|
||||
err = os.MkdirAll(dataDir, 0750)
|
||||
require.NoError(t, err)
|
||||
logsDir := filepath.Join(tmpDir, "logs")
|
||||
pluginsDir := filepath.Join(tmpDir, "plugins")
|
||||
publicDir := filepath.Join(tmpDir, "public")
|
||||
err = os.MkdirAll(publicDir, 0750)
|
||||
require.NoError(t, err)
|
||||
emailsDir := filepath.Join(publicDir, "emails")
|
||||
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "emails"), emailsDir)
|
||||
require.NoError(t, err)
|
||||
provDir := filepath.Join(cfgDir, "provisioning")
|
||||
provDSDir := filepath.Join(provDir, "datasources")
|
||||
err = os.MkdirAll(provDSDir, 0750)
|
||||
require.NoError(t, err)
|
||||
provNotifiersDir := filepath.Join(provDir, "notifiers")
|
||||
err = os.MkdirAll(provNotifiersDir, 0750)
|
||||
require.NoError(t, err)
|
||||
provPluginsDir := filepath.Join(provDir, "plugins")
|
||||
err = os.MkdirAll(provPluginsDir, 0750)
|
||||
require.NoError(t, err)
|
||||
provDashboardsDir := filepath.Join(provDir, "dashboards")
|
||||
err = os.MkdirAll(provDashboardsDir, 0750)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := ini.Empty()
|
||||
dfltSect := cfg.Section("")
|
||||
_, err = dfltSect.NewKey("app_mode", "development")
|
||||
require.NoError(t, err)
|
||||
|
||||
pathsSect, err := cfg.NewSection("paths")
|
||||
require.NoError(t, err)
|
||||
_, err = pathsSect.NewKey("data", dataDir)
|
||||
require.NoError(t, err)
|
||||
_, err = pathsSect.NewKey("logs", logsDir)
|
||||
require.NoError(t, err)
|
||||
_, err = pathsSect.NewKey("plugins", pluginsDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
logSect, err := cfg.NewSection("log")
|
||||
require.NoError(t, err)
|
||||
_, err = logSect.NewKey("level", "debug")
|
||||
require.NoError(t, err)
|
||||
|
||||
serverSect, err := cfg.NewSection("server")
|
||||
require.NoError(t, err)
|
||||
_, err = serverSect.NewKey("port", "0")
|
||||
require.NoError(t, err)
|
||||
|
||||
anonSect, err := cfg.NewSection("auth.anonymous")
|
||||
require.NoError(t, err)
|
||||
_, err = anonSect.NewKey("enabled", "true")
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgPath := filepath.Join(cfgDir, "test.ini")
|
||||
err = cfg.SaveTo(cfgPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = fs.CopyFile(filepath.Join(rootDir, "conf", "defaults.ini"), filepath.Join(cfgDir, "defaults.ini"))
|
||||
require.NoError(t, err)
|
||||
|
||||
return tmpDir, cfgPath
|
||||
}
|
||||
|
||||
func startGrafana(t *testing.T, grafDir, cfgPath string, sqlStore *sqlstore.SQLStore) string {
|
||||
t.Helper()
|
||||
|
||||
origSQLStore := registry.GetService(sqlstore.ServiceName)
|
||||
t.Cleanup(func() {
|
||||
registry.Register(origSQLStore)
|
||||
})
|
||||
registry.Register(®istry.Descriptor{
|
||||
Name: sqlstore.ServiceName,
|
||||
Instance: sqlStore,
|
||||
InitPriority: sqlstore.InitPriority,
|
||||
})
|
||||
|
||||
t.Logf("Registered SQL store %p", sqlStore)
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
server, err := server.New(server.Config{
|
||||
ConfigFile: cfgPath,
|
||||
HomePath: grafDir,
|
||||
Listener: listener,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
// Have to reset the route register between tests, since it doesn't get re-created
|
||||
server.HTTPServer.RouteRegister.Reset()
|
||||
})
|
||||
|
||||
go func() {
|
||||
// When the server runs, it will also build and initialize the service graph
|
||||
if err := server.Run(); err != nil {
|
||||
t.Log("Server exited uncleanly", "error", err)
|
||||
}
|
||||
}()
|
||||
t.Cleanup(func() {
|
||||
server.Shutdown("")
|
||||
})
|
||||
|
||||
// Wait for Grafana to be ready
|
||||
addr := listener.Addr().String()
|
||||
resp, err := http.Get(fmt.Sprintf("http://%s/api/health", addr))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
t.Cleanup(func() {
|
||||
err := resp.Body.Close()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
require.Equal(t, 200, resp.StatusCode)
|
||||
|
||||
t.Logf("Grafana is listening on %s", addr)
|
||||
|
||||
return addr
|
||||
}
|
||||
|
||||
func setUpDatabase(t *testing.T, grafDir string) *sqlstore.SQLStore {
|
||||
t.Helper()
|
||||
|
||||
sqlStore := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{
|
||||
EnsureDefaultOrgAndUser: true,
|
||||
})
|
||||
// We need the main org, since it's used for anonymous access
|
||||
org, err := sqlStore.GetOrgByName(sqlstore.MainOrgName)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, org)
|
||||
|
||||
err = sqlStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
||||
_, err := sess.Insert(&models.DataSource{
|
||||
Id: 1,
|
||||
// This will be the ID of the main org
|
||||
OrgId: 2,
|
||||
Name: "Test",
|
||||
Type: "cloudwatch",
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
})
|
||||
return err
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Make sure changes are synced with other goroutines
|
||||
err = sqlStore.Sync()
|
||||
require.NoError(t, err)
|
||||
|
||||
return sqlStore
|
||||
}
|
||||
Reference in New Issue
Block a user