bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/cloudwatch_test.go (about)

     1  package expr
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  	"time"
     7  
     8  	"bosun.org/cloudwatch"
     9  	"bosun.org/cmd/bosun/expr/parse"
    10  	"bosun.org/opentsdb"
    11  	"github.com/MiniProfiler/go/miniprofiler"
    12  	cw "github.com/aws/aws-sdk-go/service/cloudwatch"
    13  	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
    14  )
    15  
    16  type mockCloudWatchClient struct {
    17  	cloudwatchiface.CloudWatchAPI
    18  }
    19  
    20  type mockProfileProvider struct{}
    21  
    22  func (m *mockProfileProvider) NewProfile(name, region string) cloudwatchiface.CloudWatchAPI {
    23  	return &mockCloudWatchClient{}
    24  }
    25  
    26  const metric = "CPUUtilzation"
    27  const namespace = "AWS/EC2"
    28  
    29  func (c mockCloudWatchClient) ListMetricsPages(li *cw.ListMetricsInput, callback func(*cw.ListMetricsOutput, bool) bool) error {
    30  	var metrics []*cw.Metric
    31  	var n = metric
    32  	var ns = namespace
    33  
    34  	var instances []string
    35  	if li.Dimensions == nil || (li.Dimensions != nil && *li.Dimensions[0].Value == "0106b4d25c54baac7") {
    36  		instances = append(instances, "0106b4d25c54baac7")
    37  	}
    38  
    39  	if li.Dimensions == nil {
    40  		instances = append(instances, "5306b4d25c546577l")
    41  		instances = append(instances, "910asdasd25c5477l")
    42  	}
    43  
    44  	for _, inst := range instances {
    45  		dn := "InstanceId"
    46  		dv := inst
    47  		dim := cw.Dimension{
    48  			Name:  &dn,
    49  			Value: &dv,
    50  		}
    51  		dimensions := []*cw.Dimension{&dim}
    52  		metric := cw.Metric{
    53  			Dimensions: dimensions,
    54  			MetricName: &n,
    55  			Namespace:  &ns,
    56  		}
    57  		metrics = append(metrics, &metric)
    58  	}
    59  
    60  	lmo := &cw.ListMetricsOutput{
    61  		Metrics:   metrics,
    62  		NextToken: nil,
    63  	}
    64  
    65  	callback(lmo, false)
    66  	return nil
    67  }
    68  
    69  func (m *mockCloudWatchClient) GetMetricData(cwi *cw.GetMetricDataInput) (*cw.GetMetricDataOutput, error) {
    70  	var mdr []*cw.MetricDataResult
    71  	var r cw.MetricDataResult
    72  	var timestamps []*time.Time
    73  	var values []*float64
    74  
    75  	for _, mdq := range cwi.MetricDataQueries {
    76  		for j := 0; j < 600; j = j + int(*mdq.MetricStat.Period) {
    77  			time := cwi.StartTime.Add(time.Second * time.Duration(j))
    78  			timestamps = append(timestamps, &time)
    79  			val := float64(j)
    80  			values = append(values, &val)
    81  		}
    82  		r = cw.MetricDataResult{
    83  			Id:         mdq.Id,
    84  			Label:      nil,
    85  			Messages:   nil,
    86  			StatusCode: nil,
    87  			Timestamps: timestamps,
    88  			Values:     values,
    89  		}
    90  		mdr = append(mdr, &r)
    91  	}
    92  
    93  	o := cw.GetMetricDataOutput{
    94  		Messages:          nil,
    95  		MetricDataResults: mdr,
    96  		NextToken:         nil,
    97  	}
    98  	return &o, nil
    99  }
   100  
   101  func TestCloudWatchQuery(t *testing.T) {
   102  	c := cloudwatch.GetContextWithProvider(&mockProfileProvider{})
   103  
   104  	e := State{
   105  		now: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC),
   106  		Backends: &Backends{
   107  			CloudWatchContext: c,
   108  		},
   109  		BosunProviders: &BosunProviders{
   110  			Squelched: func(tags opentsdb.TagSet) bool {
   111  				return false
   112  			},
   113  		},
   114  		Timer: new(miniprofiler.Profile),
   115  	}
   116  
   117  	var tests = []struct {
   118  		region     string
   119  		namespace  string
   120  		metric     string
   121  		period     string
   122  		statistics string
   123  		dimensions string
   124  		start      string
   125  		end        string
   126  		expected   string
   127  	}{
   128  		{"eu-west-1", "AWS/EC2", "CPUUtilization", "60s", "Sum", "InstanceId:i-0106b4d25c54baac7", "2h", "1h", "{InstanceId=i-0106b4d25c54baac7}"},
   129  		{"eu-west-1", "AWS/EC2", "CPUUtilization", "1m", "Average", "InstanceId:i-0106b4d25c54baac7", "2h", "1h", "{InstanceId=i-0106b4d25c54baac7}"},
   130  		{"eu-west-1", "AWS/EC2", "CPUUtilization", "60", "Maximum", "InstanceId:i-0106b4d25c54baac7", "2h", "1h", "{InstanceId=i-0106b4d25c54baac7}"},
   131  		{"eu-west-1", "AWS/EC2", "CPUUtilization", "60", "Minimum", "InstanceId:i-0106b4d25c54baac7", "2h", "1h", "{InstanceId=i-0106b4d25c54baac7}"},
   132  		{"eu-west-1", "AWS/EC2", "CPUUtilization", "60", "Minimum", "InstanceId:*", "2h", "1h", "{InstanceId=910asdasd25c5477l}"},
   133  	}
   134  	for _, u := range tests {
   135  
   136  		results, err := CloudWatchQuery("default", &e, u.region,
   137  			u.namespace, u.metric, u.period, u.statistics,
   138  			u.dimensions, u.start, u.end)
   139  
   140  		if err != nil {
   141  			t.Errorf("Query Failure: %s ", err)
   142  		} else if results.Results[0].Group.String() != u.expected {
   143  			t.Errorf("Group mismatch got %s , expected %s", results.Results[0].Group.String(), u.expected)
   144  		}
   145  	}
   146  }
   147  
   148  func TestDateParseFail(t *testing.T) {
   149  	c := cloudwatch.GetContextWithProvider(&mockProfileProvider{})
   150  
   151  	e := State{
   152  		now: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC),
   153  		Backends: &Backends{
   154  			CloudWatchContext: c,
   155  		},
   156  		BosunProviders: &BosunProviders{
   157  			Squelched: func(tags opentsdb.TagSet) bool {
   158  				return false
   159  			},
   160  		},
   161  		Timer: new(miniprofiler.Profile),
   162  	}
   163  
   164  	var tests = []struct {
   165  		period string
   166  		start  string
   167  		end    string
   168  		err    error
   169  	}{
   170  		{"60s", "2h", "1h", nil},
   171  		{"60x", "2h", "1h", PeriodParseError},
   172  		{"60s", "2x", "1h", StartParseError},
   173  		{"60s", "2h", "1x", EndParseError},
   174  	}
   175  	for _, u := range tests {
   176  
   177  		_, err := CloudWatchQuery("default", &e, "eu-west-1", "AWS/EC2", "CPUUtilization", u.period,
   178  			"Sum", "InstanceId:i-0106b4d25c54baac7", u.start, u.end)
   179  
   180  		if err != u.err {
   181  			t.Errorf("Query Failure:  expected error to be %v, got %v", u.err, err)
   182  		}
   183  	}
   184  }
   185  
   186  func TestMultiRegion(t *testing.T) {
   187  	c := cloudwatch.GetContextWithProvider(&mockProfileProvider{})
   188  
   189  	e := State{
   190  		now: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC),
   191  		Backends: &Backends{
   192  			CloudWatchContext: c,
   193  		},
   194  		BosunProviders: &BosunProviders{
   195  			Squelched: func(tags opentsdb.TagSet) bool {
   196  				return false
   197  			},
   198  		},
   199  		Timer: new(miniprofiler.Profile),
   200  	}
   201  
   202  	var tests = []struct {
   203  		dimension string
   204  		expected  int
   205  	}{
   206  		{"eu-west-1", 1},
   207  		{"eu-central-1,eu-west-1", 2},
   208  		{"eu-west-1,eu-west-2,eu-central-1,ap-southeast-1", 4},
   209  	}
   210  	for _, u := range tests {
   211  
   212  		res, err := CloudWatchQuery("default", &e, u.dimension, "AWS/EC2", "CPUUtilization", "1m",
   213  			"Sum", "InstanceId:i-0106b4d25c54baac7", "1h", "")
   214  		if err != nil {
   215  			t.Errorf("Query Failure: %v", err)
   216  		} else if len(res.Results) != u.expected {
   217  			t.Errorf("Unexpected result set size, wanted %d, got %d results", u.expected, len(res.Results))
   218  		}
   219  	}
   220  }
   221  
   222  func TestCloudWatchQueryWithoutDimensions(t *testing.T) {
   223  	c := cloudwatch.GetContextWithProvider(&mockProfileProvider{})
   224  	e := State{
   225  		now: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC),
   226  		Backends: &Backends{
   227  			CloudWatchContext: c,
   228  		},
   229  		BosunProviders: &BosunProviders{
   230  			Squelched: func(tags opentsdb.TagSet) bool {
   231  				return false
   232  			},
   233  		},
   234  		Timer: new(miniprofiler.Profile),
   235  	}
   236  
   237  	results, err := CloudWatchQuery("default", &e, "eu-west-1", "AWS/EC2", "CPUUtilization", "60", "Sum", " ", "2h", "1h")
   238  	if err != nil {
   239  		t.Errorf("Query Failure: %s ", err)
   240  	} else if results.Results[0].Group.String() != "{}" {
   241  		t.Errorf("Dimensions not parsed correctly, expected '%s' , got '%s' ", "{}", results.Results[0].Group.String())
   242  	}
   243  }
   244  
   245  func TestParseDimensions(t *testing.T) {
   246  
   247  	var tests = []struct {
   248  		dimensionString string
   249  		dims            [][]cloudwatch.Dimension
   250  		err             error
   251  	}{
   252  		{"foo:bar", [][]cloudwatch.Dimension{{cloudwatch.Dimension{
   253  			Name:  "foo",
   254  			Value: "bar",
   255  		}}}, nil},
   256  		{"invalid", nil, DimensionParseError},
   257  	}
   258  	for _, test := range tests {
   259  		dims, err := parseDimensions(test.dimensionString)
   260  		if !reflect.DeepEqual(dims, test.dims) {
   261  			t.Errorf("Expected %+v, got %+v ", test.dims, dims)
   262  
   263  		}
   264  		if err != test.err {
   265  			t.Errorf("Expected %s, got %s ", test.err, err)
   266  		}
   267  	}
   268  
   269  }
   270  
   271  func TestCloudWatchTagQuery(t *testing.T) {
   272  	var tests = []struct {
   273  		dimensions string
   274  		tags       parse.Tags
   275  	}{
   276  		{"InstanceId:i-0106b4d25c54baac7", parse.Tags{"InstanceId": {}}},
   277  		{"InstanceId:i-0106b4d25c54baac7,AutoScalingGroupName:asg123", parse.Tags{"AutoScalingGroupName": {}, "InstanceId": {}}},
   278  		{"", parse.Tags{}},
   279  	}
   280  
   281  	args := make([]parse.Node, 8)
   282  
   283  	for _, u := range tests {
   284  		n := new(parse.StringNode)
   285  		n.Text = u.dimensions
   286  		args[5] = n
   287  		tags, err := cloudwatchTagQuery(args)
   288  		if err != nil {
   289  			t.Errorf("Error parsing tags %s", err)
   290  		}
   291  		if !tags.Equal(u.tags) {
   292  			t.Errorf("Missmatching tags, expected '%s' , got '%s' ", u.tags, tags)
   293  		}
   294  	}
   295  }