Commit 8dfbc558 by Torkel Ödegaard

Merge branch 'cloudwatch' of https://github.com/mtanda/grafana into cloudwatch

parents 3cbfe21b 9ae6ac25
......@@ -29,6 +29,11 @@
"Rev": "bed164a424e75154a40550c04c313ef51a7bb275"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/ec2query",
"Comment": "v0.7.3",
"Rev": "bed164a424e75154a40550c04c313ef51a7bb275"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/internal/protocol/query",
"Comment": "v0.7.3",
"Rev": "bed164a424e75154a40550c04c313ef51a7bb275"
......@@ -54,6 +59,11 @@
"Rev": "bed164a424e75154a40550c04c313ef51a7bb275"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v0.7.3",
"Rev": "bed164a424e75154a40550c04c313ef51a7bb275"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
},
......
// Package ec2query provides serialisation of AWS EC2 requests and responses.
package ec2query
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/input/ec2.json build_test.go
import (
"net/url"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/protocol/query/queryutil"
)
// Build builds a request for the EC2 protocol.
func Build(r *aws.Request) {
body := url.Values{
"Action": {r.Operation.Name},
"Version": {r.Service.APIVersion},
}
if err := queryutil.Parse(body, r.Params, true); err != nil {
r.Error = awserr.New("SerializationError", "failed encoding EC2 Query request", err)
}
if r.ExpireTime == 0 {
r.HTTPRequest.Method = "POST"
r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
r.SetBufferBody([]byte(body.Encode()))
} else { // This is a pre-signed request
r.HTTPRequest.Method = "GET"
r.HTTPRequest.URL.RawQuery = body.Encode()
}
}
package ec2query
//go:generate go run ../../fixtures/protocol/generate.go ../../fixtures/protocol/output/ec2.json unmarshal_test.go
import (
"encoding/xml"
"io"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/internal/protocol/xml/xmlutil"
)
// Unmarshal unmarshals a response body for the EC2 protocol.
func Unmarshal(r *aws.Request) {
defer r.HTTPResponse.Body.Close()
if r.DataFilled() {
decoder := xml.NewDecoder(r.HTTPResponse.Body)
err := xmlutil.UnmarshalXML(r.Data, decoder, "")
if err != nil {
r.Error = awserr.New("SerializationError", "failed decoding EC2 Query response", err)
return
}
}
}
// UnmarshalMeta unmarshals response headers for the EC2 protocol.
func UnmarshalMeta(r *aws.Request) {
// TODO implement unmarshaling of request IDs
}
type xmlErrorResponse struct {
XMLName xml.Name `xml:"Response"`
Code string `xml:"Errors>Error>Code"`
Message string `xml:"Errors>Error>Message"`
RequestID string `xml:"RequestId"`
}
// UnmarshalError unmarshals a response error for the EC2 protocol.
func UnmarshalError(r *aws.Request) {
defer r.HTTPResponse.Body.Close()
resp := &xmlErrorResponse{}
err := xml.NewDecoder(r.HTTPResponse.Body).Decode(resp)
if err != nil && err != io.EOF {
r.Error = awserr.New("SerializationError", "failed decoding EC2 Query error response", err)
} else {
r.Error = awserr.NewRequestFailure(
awserr.New(resp.Code, resp.Message, nil),
r.HTTPResponse.StatusCode,
resp.RequestID,
)
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
package ec2
import (
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awsutil"
)
func init() {
initRequest = func(r *aws.Request) {
if r.Operation.Name == opCopySnapshot { // fill the PresignedURL parameter
r.Handlers.Build.PushFront(fillPresignedURL)
}
}
}
func fillPresignedURL(r *aws.Request) {
if !r.ParamsFilled() {
return
}
params := r.Params.(*CopySnapshotInput)
// Stop if PresignedURL/DestinationRegion is set
if params.PresignedURL != nil || params.DestinationRegion != nil {
return
}
// First generate a copy of parameters
r.Params = awsutil.CopyOf(r.Params)
params = r.Params.(*CopySnapshotInput)
// Set destination region. Avoids infinite handler loop.
// Also needed to sign sub-request.
params.DestinationRegion = r.Service.Config.Region
// Create a new client pointing at source region.
// We will use this to presign the CopySnapshot request against
// the source region
config := r.Service.Config.Copy().
WithEndpoint("").
WithRegion(*params.SourceRegion)
client := New(config)
// Presign a CopySnapshot request with modified params
req, _ := client.CopySnapshotRequest(params)
url, err := req.Presign(300 * time.Second) // 5 minutes should be enough.
if err != nil { // bubble error back up to original request
r.Error = err
}
// We have our URL, set it on params
params.PresignedURL = &url
}
package ec2_test
import (
"io/ioutil"
"net/url"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/internal/test/unit"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/stretchr/testify/assert"
)
var _ = unit.Imported
func TestCopySnapshotPresignedURL(t *testing.T) {
svc := ec2.New(&aws.Config{Region: aws.String("us-west-2")})
assert.NotPanics(t, func() {
// Doesn't panic on nil input
req, _ := svc.CopySnapshotRequest(nil)
req.Sign()
})
req, _ := svc.CopySnapshotRequest(&ec2.CopySnapshotInput{
SourceRegion: aws.String("us-west-1"),
SourceSnapshotID: aws.String("snap-id"),
})
req.Sign()
b, _ := ioutil.ReadAll(req.HTTPRequest.Body)
q, _ := url.ParseQuery(string(b))
url, _ := url.QueryUnescape(q.Get("PresignedUrl"))
assert.Equal(t, "us-west-2", q.Get("DestinationRegion"))
assert.Regexp(t, `^https://ec2\.us-west-1\.amazon.+&DestinationRegion=us-west-2`, url)
}
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package ec2iface_test
import (
"testing"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/stretchr/testify/assert"
)
func TestInterface(t *testing.T) {
assert.Implements(t, (*ec2iface.EC2API)(nil), ec2.New(nil))
}
This source diff could not be displayed because it is too large. You can view the blob instead.
// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT.
package ec2
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/internal/protocol/ec2query"
"github.com/aws/aws-sdk-go/internal/signer/v4"
)
// Amazon Elastic Compute Cloud (Amazon EC2) provides resizable computing capacity
// in the Amazon Web Services (AWS) cloud. Using Amazon EC2 eliminates your
// need to invest in hardware up front, so you can develop and deploy applications
// faster.
type EC2 struct {
*aws.Service
}
// Used for custom service initialization logic
var initService func(*aws.Service)
// Used for custom request initialization logic
var initRequest func(*aws.Request)
// New returns a new EC2 client.
func New(config *aws.Config) *EC2 {
service := &aws.Service{
Config: aws.DefaultConfig.Merge(config),
ServiceName: "ec2",
APIVersion: "2015-04-15",
}
service.Initialize()
// Handlers
service.Handlers.Sign.PushBack(v4.Sign)
service.Handlers.Build.PushBack(ec2query.Build)
service.Handlers.Unmarshal.PushBack(ec2query.Unmarshal)
service.Handlers.UnmarshalMeta.PushBack(ec2query.UnmarshalMeta)
service.Handlers.UnmarshalError.PushBack(ec2query.UnmarshalError)
// Run custom service initialization if present
if initService != nil {
initService(service)
}
return &EC2{service}
}
// newRequest creates a new request for a EC2 operation and runs any
// custom request initialization.
func (c *EC2) newRequest(op *aws.Operation, params, data interface{}) *aws.Request {
req := aws.NewRequest(c.Service, op, params, data)
// Run custom request initialization if present
if initRequest != nil {
initRequest(req)
}
return req
}
......@@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/grafana/grafana/pkg/middleware"
)
......@@ -22,86 +23,103 @@ func ProxyCloudWatchDataSourceRequest(c *middleware.Context) {
}{}
json.Unmarshal([]byte(body), reqInfo)
svc := cloudwatch.New(&aws.Config{Region: aws.String(reqInfo.Region)})
switch reqInfo.Action {
case "GetMetricStatistics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"Namespace"`
MetricName string `json:"MetricName"`
Dimensions []map[string]string `json:"Dimensions"`
Statistics []string `json:"Statistics"`
StartTime int64 `json:"StartTime"`
EndTime int64 `json:"EndTime"`
Period int64 `json:"Period"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
statistics := make([]*string, 0)
for k := range reqParam.Parameters.Statistics {
statistics = append(statistics, &reqParam.Parameters.Statistics[k])
}
dimensions := make([]*cloudwatch.Dimension, 0)
for _, d := range reqParam.Parameters.Dimensions {
dimensions = append(dimensions, &cloudwatch.Dimension{
Name: aws.String(d["Name"]),
Value: aws.String(d["Value"]),
})
}
switch reqInfo.Service {
case "CloudWatch":
svc := cloudwatch.New(&aws.Config{Region: aws.String(reqInfo.Region)})
params := &cloudwatch.GetMetricStatisticsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: dimensions,
Statistics: statistics,
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
Period: aws.Int64(reqParam.Parameters.Period),
}
switch reqInfo.Action {
case "GetMetricStatistics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"Namespace"`
MetricName string `json:"MetricName"`
Dimensions []*cloudwatch.Dimension `json:"Dimensions"`
Statistics []*string `json:"Statistics"`
StartTime int64 `json:"StartTime"`
EndTime int64 `json:"EndTime"`
Period int64 `json:"Period"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
resp, err := svc.GetMetricStatistics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
params := &cloudwatch.GetMetricStatisticsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
Statistics: reqParam.Parameters.Statistics,
StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)),
EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)),
Period: aws.Int64(reqParam.Parameters.Period),
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
case "ListMetrics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"Namespace"`
MetricName string `json:"MetricName"`
Dimensions []map[string]string `json:"Dimensions"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
dimensions := make([]*cloudwatch.DimensionFilter, 0)
for _, d := range reqParam.Parameters.Dimensions {
dimensions = append(dimensions, &cloudwatch.DimensionFilter{
Name: aws.String(d["Name"]),
Value: aws.String(d["Value"]),
})
}
resp, err := svc.GetMetricStatistics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
params := &cloudwatch.ListMetricsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: dimensions,
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
case "ListMetrics":
reqParam := &struct {
Parameters struct {
Namespace string `json:"Namespace"`
MetricName string `json:"MetricName"`
Dimensions []*cloudwatch.DimensionFilter `json:"Dimensions"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
resp, err := svc.ListMetrics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
params := &cloudwatch.ListMetricsInput{
Namespace: aws.String(reqParam.Parameters.Namespace),
MetricName: aws.String(reqParam.Parameters.MetricName),
Dimensions: reqParam.Parameters.Dimensions,
}
resp, err := svc.ListMetrics(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
default:
c.JsonApiErr(500, "Unexpected CloudWatch action", errors.New(reqInfo.Action))
}
case "EC2":
svc := ec2.New(&aws.Config{Region: aws.String(reqInfo.Region)})
switch reqInfo.Action {
case "DescribeInstances":
reqParam := &struct {
Parameters struct {
Filters []*ec2.Filter `json:"Filters"`
InstanceIds []*string `json:"InstanceIds"`
} `json:"parameters"`
}{}
json.Unmarshal([]byte(body), reqParam)
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
params := &ec2.DescribeInstancesInput{}
if len(reqParam.Parameters.Filters) > 0 {
params.Filters = reqParam.Parameters.Filters
}
if len(reqParam.Parameters.InstanceIds) > 0 {
params.InstanceIDs = reqParam.Parameters.InstanceIds
}
resp, err := svc.DescribeInstances(params)
if err != nil {
c.JsonApiErr(500, "Unable to call AWS API", err)
return
}
respJson, _ := json.Marshal(resp)
fmt.Fprint(c.RW(), string(respJson))
default:
c.JsonApiErr(500, "Unexpected EC2 action", errors.New(reqInfo.Action))
}
default:
c.JsonApiErr(500, "Unexpected CloudWatch action", errors.New(reqInfo.Action))
c.JsonApiErr(500, "Unexpected service", errors.New(reqInfo.Service))
}
}
......@@ -2,13 +2,12 @@
define([
'angular',
'lodash',
'app/core/utils/datemath',
'moment',
'./queryCtrl',
'./directives',
'aws-sdk',
],
function (angular, _, dateMath) {
function (angular, _) {
'use strict';
var module = angular.module('grafana.services');
......@@ -275,7 +274,7 @@ function (angular, _, dateMath) {
};
CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
var cloudwatch = this.getCloudWatchClient(query.region);
var cloudwatch = this.getAwsClient(query.region, 'CloudWatch');
var params = {
Namespace: query.namespace,
......@@ -321,7 +320,7 @@ function (angular, _, dateMath) {
namespace = templateSrv.replace(namespace);
metricName = templateSrv.replace(metricName);
var cloudwatch = this.getCloudWatchClient(region);
var cloudwatch = this.getAwsClient(region, 'CloudWatch');
var params = {
Namespace: namespace,
......@@ -353,6 +352,30 @@ function (angular, _, dateMath) {
return d.promise;
};
CloudWatchDatasource.prototype.performEC2DescribeInstances = function(region, filters, instanceIds) {
var ec2 = this.getAwsClient(region, 'EC2');
var params = {};
if (filters.length > 0) {
params.Filters = filters;
}
if (instanceIds.length > 0) {
params.InstanceIds = instanceIds;
}
var d = $q.defer();
ec2.describeInstances(params, function(err, data) {
if (err) {
return d.reject(err);
}
return d.resolve(data);
});
return d.promise;
};
CloudWatchDatasource.prototype.getTemplateVariableNames = function() {
var variables = [];
templateSrv.fillVariableValuesForUrl(variables);
......@@ -436,6 +459,24 @@ function (angular, _, dateMath) {
});
}
var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
if (ebsVolumeIdsQuery) {
region = templateSrv.replace(ebsVolumeIdsQuery[1]);
var instanceId = templateSrv.replace(ebsVolumeIdsQuery[2]);
var instanceIds = [
instanceId
];
return this.performEC2DescribeInstances(region, [], instanceIds)
.then(function(result) {
var volumeIds = _.map(result.Reservations[0].Instances[0].BlockDeviceMappings, function(mapping) {
return mapping.EBS.VolumeID;
});
return transformSuggestData(volumeIds);
});
}
return $q.when([]);
};
......@@ -451,9 +492,9 @@ function (angular, _, dateMath) {
});
};
CloudWatchDatasource.prototype.getCloudWatchClient = function(region) {
CloudWatchDatasource.prototype.getAwsClient = function(region, service) {
if (!this.proxyMode) {
return new AWS.CloudWatch({
return new AWS[service]({
region: region,
accessKeyId: this.credentials.accessKeyId,
secretAccessKey: this.credentials.secretAccessKey
......@@ -483,10 +524,22 @@ function (angular, _, dateMath) {
};
};
return {
getMetricStatistics: generateRequestProxy('CloudWatch', 'GetMetricStatistics'),
listMetrics: generateRequestProxy('CloudWatch', 'ListMetrics')
};
var proxy;
switch (service) {
case 'CloudWatch':
proxy = {
getMetricStatistics: generateRequestProxy('CloudWatch', 'GetMetricStatistics'),
listMetrics: generateRequestProxy('CloudWatch', 'ListMetrics')
};
break;
case 'EC2':
proxy = {
describeInstances: generateRequestProxy('EC2', 'DescribeInstances')
};
break;
}
return proxy;
}
};
......@@ -543,7 +596,7 @@ function (angular, _, dateMath) {
}
function convertToCloudWatchTime(date) {
return date.valueOf() / 1000;
return Math.round(date.valueOf() / 1000);
}
function convertDimensionFormat(dimensions) {
......
......@@ -30,7 +30,7 @@
</ul>
<div class="clearfix"></div>
</div>
<div class="tight-form">
<div class="tight-form" ng-show="current.jsonData.access === 'direct'">
<ul class="tight-form-list">
<li class="tight-form-item" style="width: 160px">
Secret Access Key
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment