azuremonitor-datasource_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. package azuremonitor
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/url"
  7. "testing"
  8. "time"
  9. "github.com/grafana/grafana/pkg/components/simplejson"
  10. "github.com/grafana/grafana/pkg/models"
  11. "github.com/grafana/grafana/pkg/tsdb"
  12. . "github.com/smartystreets/goconvey/convey"
  13. )
  14. func TestAzureMonitorDatasource(t *testing.T) {
  15. Convey("AzureMonitorDatasource", t, func() {
  16. datasource := &AzureMonitorDatasource{}
  17. Convey("Parse queries from frontend and build AzureMonitor API queries", func() {
  18. fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local)
  19. tsdbQuery := &tsdb.TsdbQuery{
  20. TimeRange: &tsdb.TimeRange{
  21. From: fmt.Sprintf("%v", fromStart.Unix()*1000),
  22. To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000),
  23. },
  24. Queries: []*tsdb.Query{
  25. {
  26. DataSource: &models.DataSource{
  27. JsonData: simplejson.NewFromAny(map[string]interface{}{
  28. "subscriptionId": "default-subscription",
  29. }),
  30. },
  31. Model: simplejson.NewFromAny(map[string]interface{}{
  32. "subscription": "12345678-aaaa-bbbb-cccc-123456789abc",
  33. "azureMonitor": map[string]interface{}{
  34. "queryMode": "singleResource",
  35. "data": map[string]interface{}{
  36. "singleResource": map[string]interface{}{
  37. "timeGrain": "PT1M",
  38. "aggregation": "Average",
  39. "resourceGroup": "grafanastaging",
  40. "resourceName": "grafana",
  41. "metricDefinition": "Microsoft.Compute/virtualMachines",
  42. "metricNamespace": "Microsoft.Compute-virtualMachines",
  43. "metricName": "Percentage CPU",
  44. "alias": "testalias",
  45. "queryType": "Azure Monitor",
  46. },
  47. },
  48. },
  49. }),
  50. RefId: "A",
  51. },
  52. },
  53. }
  54. Convey("and is a normal query", func() {
  55. queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
  56. So(err, ShouldBeNil)
  57. So(len(queries), ShouldEqual, 1)
  58. So(queries[0].RefID, ShouldEqual, "A")
  59. So(queries[0].URL, ShouldEqual, "12345678-aaaa-bbbb-cccc-123456789abc/resourceGroups/grafanastaging/providers/Microsoft.Compute/virtualMachines/grafana/providers/microsoft.insights/metrics")
  60. So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
  61. So(len(queries[0].Params), ShouldEqual, 6)
  62. So(queries[0].Params["timespan"][0], ShouldEqual, "2018-03-15T13:00:00Z/2018-03-15T13:34:00Z")
  63. So(queries[0].Params["api-version"][0], ShouldEqual, "2018-01-01")
  64. So(queries[0].Params["aggregation"][0], ShouldEqual, "Average")
  65. So(queries[0].Params["metricnames"][0], ShouldEqual, "Percentage CPU")
  66. So(queries[0].Params["interval"][0], ShouldEqual, "PT1M")
  67. So(queries[0].Alias, ShouldEqual, "testalias")
  68. })
  69. Convey("and has a time grain set to auto", func() {
  70. tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
  71. "azureMonitor": map[string]interface{}{
  72. "timeGrain": "auto",
  73. "aggregation": "Average",
  74. "resourceGroup": "grafanastaging",
  75. "resourceName": "grafana",
  76. "metricDefinition": "Microsoft.Compute/virtualMachines",
  77. "metricNamespace": "Microsoft.Compute-virtualMachines",
  78. "metricName": "Percentage CPU",
  79. "alias": "testalias",
  80. "queryType": "Azure Monitor",
  81. },
  82. })
  83. tsdbQuery.Queries[0].IntervalMs = 400000
  84. queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
  85. So(err, ShouldBeNil)
  86. So(queries[0].Params["interval"][0], ShouldEqual, "PT15M")
  87. })
  88. Convey("and has a time grain set to auto and the metric has a limited list of allowed time grains", func() {
  89. tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
  90. "azureMonitor": map[string]interface{}{
  91. "timeGrain": "auto",
  92. "aggregation": "Average",
  93. "resourceGroup": "grafanastaging",
  94. "resourceName": "grafana",
  95. "metricDefinition": "Microsoft.Compute/virtualMachines",
  96. "metricNamespace": "Microsoft.Compute-virtualMachines",
  97. "metricName": "Percentage CPU",
  98. "alias": "testalias",
  99. "queryType": "Azure Monitor",
  100. "allowedTimeGrainsMs": []interface{}{"auto", json.Number("60000"), json.Number("300000")},
  101. },
  102. })
  103. tsdbQuery.Queries[0].IntervalMs = 400000
  104. queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
  105. So(err, ShouldBeNil)
  106. So(queries[0].Params["interval"][0], ShouldEqual, "PT5M")
  107. })
  108. Convey("and has a dimension filter", func() {
  109. tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
  110. "azureMonitor": map[string]interface{}{
  111. "timeGrain": "PT1M",
  112. "aggregation": "Average",
  113. "resourceGroup": "grafanastaging",
  114. "resourceName": "grafana",
  115. "metricDefinition": "Microsoft.Compute/virtualMachines",
  116. "metricNamespace": "Microsoft.Compute-virtualMachines",
  117. "metricName": "Percentage CPU",
  118. "alias": "testalias",
  119. "queryType": "Azure Monitor",
  120. "dimension": "blob",
  121. "dimensionFilter": "*",
  122. },
  123. })
  124. queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
  125. So(err, ShouldBeNil)
  126. So(queries[0].Target, ShouldEqual, "%24filter=blob+eq+%27%2A%27&aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
  127. })
  128. Convey("and has a dimension filter set to None", func() {
  129. tsdbQuery.Queries[0].Model = simplejson.NewFromAny(map[string]interface{}{
  130. "azureMonitor": map[string]interface{}{
  131. "timeGrain": "PT1M",
  132. "aggregation": "Average",
  133. "resourceGroup": "grafanastaging",
  134. "resourceName": "grafana",
  135. "metricDefinition": "Microsoft.Compute/virtualMachines",
  136. "metricNamespace": "Microsoft.Compute-virtualMachines",
  137. "metricName": "Percentage CPU",
  138. "alias": "testalias",
  139. "queryType": "Azure Monitor",
  140. "dimension": "None",
  141. "dimensionFilter": "*",
  142. },
  143. })
  144. queries, err := datasource.buildQueries(tsdbQuery.Queries, tsdbQuery.TimeRange)
  145. So(err, ShouldBeNil)
  146. So(queries[0].Target, ShouldEqual, "aggregation=Average&api-version=2018-01-01&interval=PT1M&metricnames=Percentage+CPU&metricnamespace=Microsoft.Compute-virtualMachines&timespan=2018-03-15T13%3A00%3A00Z%2F2018-03-15T13%3A34%3A00Z")
  147. })
  148. })
  149. Convey("Parse AzureMonitor API response in the time series format", func() {
  150. Convey("when data from query aggregated as average to one time series", func() {
  151. data, err := loadTestFile("./test-data/1-azure-monitor-response-avg.json")
  152. So(err, ShouldBeNil)
  153. So(data.Interval, ShouldEqual, "PT1M")
  154. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  155. query := &AzureMonitorQuery{
  156. UrlComponents: map[string]string{
  157. "resourceName": "grafana",
  158. },
  159. Params: url.Values{
  160. "aggregation": {"Average"},
  161. },
  162. }
  163. err = datasource.parseResponse(res, data, query)
  164. So(err, ShouldBeNil)
  165. So(len(res.Series), ShouldEqual, 1)
  166. So(res.Series[0].Name, ShouldEqual, "grafana.Percentage CPU")
  167. So(len(res.Series[0].Points), ShouldEqual, 5)
  168. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 2.0875)
  169. So(res.Series[0].Points[0][1].Float64, ShouldEqual, int64(1549620780000))
  170. So(res.Series[0].Points[1][0].Float64, ShouldEqual, 2.1525)
  171. So(res.Series[0].Points[1][1].Float64, ShouldEqual, int64(1549620840000))
  172. So(res.Series[0].Points[2][0].Float64, ShouldEqual, 2.155)
  173. So(res.Series[0].Points[2][1].Float64, ShouldEqual, int64(1549620900000))
  174. So(res.Series[0].Points[3][0].Float64, ShouldEqual, 3.6925)
  175. So(res.Series[0].Points[3][1].Float64, ShouldEqual, int64(1549620960000))
  176. So(res.Series[0].Points[4][0].Float64, ShouldEqual, 2.44)
  177. So(res.Series[0].Points[4][1].Float64, ShouldEqual, int64(1549621020000))
  178. })
  179. Convey("when data from query aggregated as total to one time series", func() {
  180. data, err := loadTestFile("./test-data/2-azure-monitor-response-total.json")
  181. So(err, ShouldBeNil)
  182. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  183. query := &AzureMonitorQuery{
  184. UrlComponents: map[string]string{
  185. "resourceName": "grafana",
  186. },
  187. Params: url.Values{
  188. "aggregation": {"Total"},
  189. },
  190. }
  191. err = datasource.parseResponse(res, data, query)
  192. So(err, ShouldBeNil)
  193. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 8.26)
  194. So(res.Series[0].Points[0][1].Float64, ShouldEqual, int64(1549718940000))
  195. })
  196. Convey("when data from query aggregated as maximum to one time series", func() {
  197. data, err := loadTestFile("./test-data/3-azure-monitor-response-maximum.json")
  198. So(err, ShouldBeNil)
  199. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  200. query := &AzureMonitorQuery{
  201. UrlComponents: map[string]string{
  202. "resourceName": "grafana",
  203. },
  204. Params: url.Values{
  205. "aggregation": {"Maximum"},
  206. },
  207. }
  208. err = datasource.parseResponse(res, data, query)
  209. So(err, ShouldBeNil)
  210. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3.07)
  211. So(res.Series[0].Points[0][1].Float64, ShouldEqual, int64(1549722360000))
  212. })
  213. Convey("when data from query aggregated as minimum to one time series", func() {
  214. data, err := loadTestFile("./test-data/4-azure-monitor-response-minimum.json")
  215. So(err, ShouldBeNil)
  216. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  217. query := &AzureMonitorQuery{
  218. UrlComponents: map[string]string{
  219. "resourceName": "grafana",
  220. },
  221. Params: url.Values{
  222. "aggregation": {"Minimum"},
  223. },
  224. }
  225. err = datasource.parseResponse(res, data, query)
  226. So(err, ShouldBeNil)
  227. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 1.51)
  228. So(res.Series[0].Points[0][1].Float64, ShouldEqual, int64(1549723380000))
  229. })
  230. Convey("when data from query aggregated as Count to one time series", func() {
  231. data, err := loadTestFile("./test-data/5-azure-monitor-response-count.json")
  232. So(err, ShouldBeNil)
  233. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  234. query := &AzureMonitorQuery{
  235. UrlComponents: map[string]string{
  236. "resourceName": "grafana",
  237. },
  238. Params: url.Values{
  239. "aggregation": {"Count"},
  240. },
  241. }
  242. err = datasource.parseResponse(res, data, query)
  243. So(err, ShouldBeNil)
  244. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 4)
  245. So(res.Series[0].Points[0][1].Float64, ShouldEqual, int64(1549723440000))
  246. })
  247. Convey("when data from query aggregated as total and has dimension filter", func() {
  248. data, err := loadTestFile("./test-data/6-azure-monitor-response-multi-dimension.json")
  249. So(err, ShouldBeNil)
  250. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  251. query := &AzureMonitorQuery{
  252. UrlComponents: map[string]string{
  253. "resourceName": "grafana",
  254. },
  255. Params: url.Values{
  256. "aggregation": {"Average"},
  257. },
  258. }
  259. err = datasource.parseResponse(res, data, query)
  260. So(err, ShouldBeNil)
  261. So(len(res.Series), ShouldEqual, 3)
  262. So(res.Series[0].Name, ShouldEqual, "grafana{blobtype=PageBlob}.Blob Count")
  263. So(res.Series[0].Points[0][0].Float64, ShouldEqual, 3)
  264. So(res.Series[1].Name, ShouldEqual, "grafana{blobtype=BlockBlob}.Blob Count")
  265. So(res.Series[1].Points[0][0].Float64, ShouldEqual, 1)
  266. So(res.Series[2].Name, ShouldEqual, "grafana{blobtype=Azure Data Lake Storage}.Blob Count")
  267. So(res.Series[2].Points[0][0].Float64, ShouldEqual, 0)
  268. })
  269. Convey("when data from query has alias patterns", func() {
  270. data, err := loadTestFile("./test-data/2-azure-monitor-response-total.json")
  271. So(err, ShouldBeNil)
  272. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  273. query := &AzureMonitorQuery{
  274. Alias: "custom {{resourcegroup}} {{namespace}} {{resourceName}} {{metric}}",
  275. UrlComponents: map[string]string{
  276. "resourceName": "grafana",
  277. },
  278. Params: url.Values{
  279. "aggregation": {"Total"},
  280. },
  281. }
  282. err = datasource.parseResponse(res, data, query)
  283. So(err, ShouldBeNil)
  284. So(res.Series[0].Name, ShouldEqual, "custom grafanastaging Microsoft.Compute/virtualMachines grafana Percentage CPU")
  285. })
  286. Convey("when data has dimension filters and alias patterns", func() {
  287. data, err := loadTestFile("./test-data/6-azure-monitor-response-multi-dimension.json")
  288. So(err, ShouldBeNil)
  289. res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"}
  290. query := &AzureMonitorQuery{
  291. Alias: "{{dimensionname}}={{DimensionValue}}",
  292. UrlComponents: map[string]string{
  293. "resourceName": "grafana",
  294. },
  295. Params: url.Values{
  296. "aggregation": {"Average"},
  297. },
  298. }
  299. err = datasource.parseResponse(res, data, query)
  300. So(err, ShouldBeNil)
  301. So(res.Series[0].Name, ShouldEqual, "blobtype=PageBlob")
  302. So(res.Series[1].Name, ShouldEqual, "blobtype=BlockBlob")
  303. So(res.Series[2].Name, ShouldEqual, "blobtype=Azure Data Lake Storage")
  304. })
  305. })
  306. Convey("Find closest allowed interval for auto time grain", func() {
  307. intervals := map[string]int64{
  308. "3m": 180000,
  309. "5m": 300000,
  310. "10m": 600000,
  311. "15m": 900000,
  312. "1d": 86400000,
  313. "2d": 172800000,
  314. }
  315. closest := datasource.findClosestAllowedIntervalMS(intervals["3m"], []int64{})
  316. So(closest, ShouldEqual, intervals["5m"])
  317. closest = datasource.findClosestAllowedIntervalMS(intervals["10m"], []int64{})
  318. So(closest, ShouldEqual, intervals["15m"])
  319. closest = datasource.findClosestAllowedIntervalMS(intervals["2d"], []int64{})
  320. So(closest, ShouldEqual, intervals["1d"])
  321. closest = datasource.findClosestAllowedIntervalMS(intervals["3m"], []int64{intervals["1d"]})
  322. So(closest, ShouldEqual, intervals["1d"])
  323. })
  324. })
  325. }
  326. func loadTestFile(path string) (AzureMonitorResponse, error) {
  327. var data AzureMonitorResponse
  328. jsonBody, err := ioutil.ReadFile(path)
  329. if err != nil {
  330. return data, err
  331. }
  332. err = json.Unmarshal(jsonBody, &data)
  333. return data, err
  334. }