extractor_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. package alerting
  2. import (
  3. "testing"
  4. "github.com/grafana/grafana/pkg/bus"
  5. "github.com/grafana/grafana/pkg/components/simplejson"
  6. m "github.com/grafana/grafana/pkg/models"
  7. "github.com/grafana/grafana/pkg/setting"
  8. . "github.com/smartystreets/goconvey/convey"
  9. )
  10. func TestAlertRuleExtraction(t *testing.T) {
  11. Convey("Parsing alert rules from dashboard json", t, func() {
  12. RegisterCondition("query", func(model *simplejson.Json, index int) (Condition, error) {
  13. return &FakeCondition{}, nil
  14. })
  15. setting.NewConfigContext(&setting.CommandLineArgs{
  16. HomePath: "../../../",
  17. })
  18. // mock data
  19. defaultDs := &m.DataSource{Id: 12, OrgId: 1, Name: "I am default", IsDefault: true}
  20. graphite2Ds := &m.DataSource{Id: 15, OrgId: 1, Name: "graphite2"}
  21. influxDBDs := &m.DataSource{Id: 16, OrgId: 1, Name: "InfluxDB"}
  22. bus.AddHandler("test", func(query *m.GetDataSourcesQuery) error {
  23. query.Result = []*m.DataSource{defaultDs, graphite2Ds}
  24. return nil
  25. })
  26. bus.AddHandler("test", func(query *m.GetDataSourceByNameQuery) error {
  27. if query.Name == defaultDs.Name {
  28. query.Result = defaultDs
  29. }
  30. if query.Name == graphite2Ds.Name {
  31. query.Result = graphite2Ds
  32. }
  33. if query.Name == influxDBDs.Name {
  34. query.Result = influxDBDs
  35. }
  36. return nil
  37. })
  38. json := `
  39. {
  40. "id": 57,
  41. "title": "Graphite 4",
  42. "originalTitle": "Graphite 4",
  43. "tags": ["graphite"],
  44. "rows": [
  45. {
  46. "panels": [
  47. {
  48. "title": "Active desktop users",
  49. "editable": true,
  50. "type": "graph",
  51. "id": 3,
  52. "targets": [
  53. {
  54. "refId": "A",
  55. "target": "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)"
  56. }
  57. ],
  58. "datasource": null,
  59. "alert": {
  60. "name": "name1",
  61. "message": "desc1",
  62. "handler": 1,
  63. "frequency": "60s",
  64. "conditions": [
  65. {
  66. "type": "query",
  67. "query": {"params": ["A", "5m", "now"]},
  68. "reducer": {"type": "avg", "params": []},
  69. "evaluator": {"type": ">", "params": [100]}
  70. }
  71. ]
  72. }
  73. },
  74. {
  75. "title": "Active mobile users",
  76. "id": 4,
  77. "targets": [
  78. {"refId": "A", "target": ""},
  79. {"refId": "B", "target": "aliasByNode(statsd.fakesite.counters.session_start.mobile.count, 4)"}
  80. ],
  81. "datasource": "graphite2",
  82. "alert": {
  83. "name": "name2",
  84. "message": "desc2",
  85. "handler": 0,
  86. "frequency": "60s",
  87. "severity": "warning",
  88. "conditions": [
  89. {
  90. "type": "query",
  91. "query": {"params": ["B", "5m", "now"]},
  92. "reducer": {"type": "avg", "params": []},
  93. "evaluator": {"type": ">", "params": [100]}
  94. }
  95. ]
  96. }
  97. }
  98. ]
  99. }
  100. ]
  101. }`
  102. Convey("Extractor should not modify the original json", func() {
  103. dashJson, err := simplejson.NewJson([]byte(json))
  104. So(err, ShouldBeNil)
  105. dash := m.NewDashboardFromJson(dashJson)
  106. getTarget := func(j *simplejson.Json) string {
  107. rowObj := j.Get("rows").MustArray()[0]
  108. row := simplejson.NewFromAny(rowObj)
  109. panelObj := row.Get("panels").MustArray()[0]
  110. panel := simplejson.NewFromAny(panelObj)
  111. conditionObj := panel.Get("alert").Get("conditions").MustArray()[0]
  112. condition := simplejson.NewFromAny(conditionObj)
  113. return condition.Get("query").Get("model").Get("target").MustString()
  114. }
  115. Convey("Dashboard json rows.panels.alert.query.model.target should be empty", func() {
  116. So(getTarget(dashJson), ShouldEqual, "")
  117. })
  118. extractor := NewDashAlertExtractor(dash, 1)
  119. _, _ = extractor.GetAlerts()
  120. Convey("Dashboard json should not be updated after extracting rules", func() {
  121. So(getTarget(dashJson), ShouldEqual, "")
  122. })
  123. })
  124. Convey("Parsing and validating dashboard containing graphite alerts", func() {
  125. dashJson, err := simplejson.NewJson([]byte(json))
  126. So(err, ShouldBeNil)
  127. dash := m.NewDashboardFromJson(dashJson)
  128. extractor := NewDashAlertExtractor(dash, 1)
  129. alerts, err := extractor.GetAlerts()
  130. Convey("Get rules without error", func() {
  131. So(err, ShouldBeNil)
  132. })
  133. Convey("all properties have been set", func() {
  134. So(len(alerts), ShouldEqual, 2)
  135. for _, v := range alerts {
  136. So(v.DashboardId, ShouldEqual, 57)
  137. So(v.Name, ShouldNotBeEmpty)
  138. So(v.Message, ShouldNotBeEmpty)
  139. settings := simplejson.NewFromAny(v.Settings)
  140. So(settings.Get("interval").MustString(""), ShouldEqual, "")
  141. }
  142. Convey("should extract handler property", func() {
  143. So(alerts[0].Handler, ShouldEqual, 1)
  144. So(alerts[1].Handler, ShouldEqual, 0)
  145. })
  146. Convey("should extract frequency in seconds", func() {
  147. So(alerts[0].Frequency, ShouldEqual, 60)
  148. So(alerts[1].Frequency, ShouldEqual, 60)
  149. })
  150. Convey("should extract panel idc", func() {
  151. So(alerts[0].PanelId, ShouldEqual, 3)
  152. So(alerts[1].PanelId, ShouldEqual, 4)
  153. })
  154. Convey("should extract name and desc", func() {
  155. So(alerts[0].Name, ShouldEqual, "name1")
  156. So(alerts[0].Message, ShouldEqual, "desc1")
  157. So(alerts[1].Name, ShouldEqual, "name2")
  158. So(alerts[1].Message, ShouldEqual, "desc2")
  159. })
  160. Convey("should set datasourceId", func() {
  161. condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0])
  162. query := condition.Get("query")
  163. So(query.Get("datasourceId").MustInt64(), ShouldEqual, 12)
  164. })
  165. Convey("should copy query model to condition", func() {
  166. condition := simplejson.NewFromAny(alerts[0].Settings.Get("conditions").MustArray()[0])
  167. model := condition.Get("query").Get("model")
  168. So(model.Get("target").MustString(), ShouldEqual, "aliasByNode(statsd.fakesite.counters.session_start.desktop.count, 4)")
  169. })
  170. })
  171. })
  172. Convey("Parse and validate dashboard containing influxdb alert", func() {
  173. json2 := `{
  174. "id": 4,
  175. "title": "Influxdb",
  176. "tags": [
  177. "apa"
  178. ],
  179. "style": "dark",
  180. "timezone": "browser",
  181. "editable": true,
  182. "hideControls": false,
  183. "sharedCrosshair": false,
  184. "rows": [
  185. {
  186. "collapse": false,
  187. "editable": true,
  188. "height": "450px",
  189. "panels": [
  190. {
  191. "alert": {
  192. "conditions": [
  193. {
  194. "evaluator": {
  195. "params": [
  196. 10
  197. ],
  198. "type": "gt"
  199. },
  200. "query": {
  201. "params": [
  202. "B",
  203. "5m",
  204. "now"
  205. ]
  206. },
  207. "reducer": {
  208. "params": [],
  209. "type": "avg"
  210. },
  211. "type": "query"
  212. }
  213. ],
  214. "frequency": "3s",
  215. "handler": 1,
  216. "name": "Influxdb",
  217. "noDataState": "no_data",
  218. "notifications": [
  219. {
  220. "id": 6
  221. }
  222. ]
  223. },
  224. "alerting": {},
  225. "aliasColors": {
  226. "logins.count.count": "#890F02"
  227. },
  228. "bars": false,
  229. "datasource": "InfluxDB",
  230. "editable": true,
  231. "error": false,
  232. "fill": 1,
  233. "grid": {},
  234. "id": 1,
  235. "interval": ">10s",
  236. "isNew": true,
  237. "legend": {
  238. "avg": false,
  239. "current": false,
  240. "max": false,
  241. "min": false,
  242. "show": true,
  243. "total": false,
  244. "values": false
  245. },
  246. "lines": true,
  247. "linewidth": 2,
  248. "links": [],
  249. "nullPointMode": "connected",
  250. "percentage": false,
  251. "pointradius": 5,
  252. "points": false,
  253. "renderer": "flot",
  254. "seriesOverrides": [],
  255. "span": 10,
  256. "stack": false,
  257. "steppedLine": false,
  258. "targets": [
  259. {
  260. "dsType": "influxdb",
  261. "groupBy": [
  262. {
  263. "params": [
  264. "$interval"
  265. ],
  266. "type": "time"
  267. },
  268. {
  269. "params": [
  270. "datacenter"
  271. ],
  272. "type": "tag"
  273. },
  274. {
  275. "params": [
  276. "none"
  277. ],
  278. "type": "fill"
  279. }
  280. ],
  281. "hide": false,
  282. "measurement": "logins.count",
  283. "policy": "default",
  284. "query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
  285. "rawQuery": true,
  286. "refId": "B",
  287. "resultFormat": "time_series",
  288. "select": [
  289. [
  290. {
  291. "params": [
  292. "value"
  293. ],
  294. "type": "field"
  295. },
  296. {
  297. "params": [],
  298. "type": "count"
  299. }
  300. ]
  301. ],
  302. "tags": []
  303. },
  304. {
  305. "dsType": "influxdb",
  306. "groupBy": [
  307. {
  308. "params": [
  309. "$interval"
  310. ],
  311. "type": "time"
  312. },
  313. {
  314. "params": [
  315. "null"
  316. ],
  317. "type": "fill"
  318. }
  319. ],
  320. "hide": true,
  321. "measurement": "cpu",
  322. "policy": "default",
  323. "refId": "A",
  324. "resultFormat": "time_series",
  325. "select": [
  326. [
  327. {
  328. "params": [
  329. "value"
  330. ],
  331. "type": "field"
  332. },
  333. {
  334. "params": [],
  335. "type": "mean"
  336. }
  337. ],
  338. [
  339. {
  340. "params": [
  341. "value"
  342. ],
  343. "type": "field"
  344. },
  345. {
  346. "params": [],
  347. "type": "sum"
  348. }
  349. ]
  350. ],
  351. "tags": []
  352. }
  353. ],
  354. "thresholds": [
  355. {
  356. "colorMode": "critical",
  357. "fill": true,
  358. "line": true,
  359. "op": "gt",
  360. "value": 10
  361. }
  362. ],
  363. "timeFrom": null,
  364. "timeShift": null,
  365. "title": "Panel Title",
  366. "tooltip": {
  367. "msResolution": false,
  368. "ordering": "alphabetical",
  369. "shared": true,
  370. "sort": 0,
  371. "value_type": "cumulative"
  372. },
  373. "type": "graph",
  374. "xaxis": {
  375. "mode": "time",
  376. "name": null,
  377. "show": true,
  378. "values": []
  379. },
  380. "yaxes": [
  381. {
  382. "format": "short",
  383. "logBase": 1,
  384. "max": null,
  385. "min": null,
  386. "show": true
  387. },
  388. {
  389. "format": "short",
  390. "logBase": 1,
  391. "max": null,
  392. "min": null,
  393. "show": true
  394. }
  395. ]
  396. },
  397. {
  398. "editable": true,
  399. "error": false,
  400. "id": 2,
  401. "isNew": true,
  402. "limit": 10,
  403. "links": [],
  404. "show": "current",
  405. "span": 2,
  406. "stateFilter": [
  407. "alerting"
  408. ],
  409. "title": "Alert status",
  410. "type": "alertlist"
  411. }
  412. ],
  413. "title": "Row"
  414. }
  415. ],
  416. "time": {
  417. "from": "now-5m",
  418. "to": "now"
  419. },
  420. "timepicker": {
  421. "now": true,
  422. "refresh_intervals": [
  423. "5s",
  424. "10s",
  425. "30s",
  426. "1m",
  427. "5m",
  428. "15m",
  429. "30m",
  430. "1h",
  431. "2h",
  432. "1d"
  433. ],
  434. "time_options": [
  435. "5m",
  436. "15m",
  437. "1h",
  438. "6h",
  439. "12h",
  440. "24h",
  441. "2d",
  442. "7d",
  443. "30d"
  444. ]
  445. },
  446. "templating": {
  447. "list": []
  448. },
  449. "annotations": {
  450. "list": []
  451. },
  452. "schemaVersion": 13,
  453. "version": 120,
  454. "links": [],
  455. "gnetId": null
  456. }`
  457. dashJson, err := simplejson.NewJson([]byte(json2))
  458. So(err, ShouldBeNil)
  459. dash := m.NewDashboardFromJson(dashJson)
  460. extractor := NewDashAlertExtractor(dash, 1)
  461. alerts, err := extractor.GetAlerts()
  462. Convey("Get rules without error", func() {
  463. So(err, ShouldBeNil)
  464. })
  465. Convey("should be able to read interval", func() {
  466. So(len(alerts), ShouldEqual, 1)
  467. for _, alert := range alerts {
  468. So(alert.DashboardId, ShouldEqual, 4)
  469. conditions := alert.Settings.Get("conditions").MustArray()
  470. cond := simplejson.NewFromAny(conditions[0])
  471. So(cond.Get("query").Get("model").Get("interval").MustString(), ShouldEqual, ">10s")
  472. }
  473. })
  474. })
  475. })
  476. }