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 }