test_zabbix.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. from flexmock import flexmock
  2. import borgmatic.hooks.monitoring.monitor
  3. from borgmatic.hooks.monitoring import zabbix as module
  4. SERVER = 'https://zabbix.com/zabbix/api_jsonrpc.php'
  5. ITEMID = 55105
  6. USERNAME = 'testuser'
  7. PASSWORD = 'fakepassword'
  8. API_KEY = 'fakekey'
  9. HOST = 'borg-server'
  10. KEY = 'borg.status'
  11. VALUE = 'fail'
  12. DATA_HOST_KEY = {
  13. 'jsonrpc': '2.0',
  14. 'method': 'history.push',
  15. 'params': {'host': HOST, 'key': KEY, 'value': VALUE},
  16. 'id': 1,
  17. }
  18. DATA_HOST_KEY_WITH_KEY_VALUE = {
  19. 'jsonrpc': '2.0',
  20. 'method': 'history.push',
  21. 'params': {'host': HOST, 'key': KEY, 'value': VALUE},
  22. 'id': 1,
  23. }
  24. DATA_ITEMID = {
  25. 'jsonrpc': '2.0',
  26. 'method': 'history.push',
  27. 'params': {'itemid': ITEMID, 'value': VALUE},
  28. 'id': 1,
  29. }
  30. DATA_HOST_KEY_WITH_ITEMID = {
  31. 'jsonrpc': '2.0',
  32. 'method': 'history.push',
  33. 'params': {'itemid': ITEMID, 'value': VALUE},
  34. 'id': 1,
  35. }
  36. DATA_USER_LOGIN = {
  37. 'jsonrpc': '2.0',
  38. 'method': 'user.login',
  39. 'params': {'username': USERNAME, 'password': PASSWORD},
  40. 'id': 1,
  41. }
  42. DATA_USER_LOGOUT = {
  43. 'jsonrpc': '2.0',
  44. 'method': 'user.logout',
  45. 'params': [],
  46. 'id': 1,
  47. }
  48. AUTH_HEADERS_LOGIN = {
  49. 'Content-Type': 'application/json-rpc',
  50. }
  51. AUTH_HEADERS = {
  52. 'Content-Type': 'application/json-rpc',
  53. 'Authorization': f'Bearer {API_KEY}',
  54. }
  55. def test_send_zabbix_request_with_post_error_bails():
  56. server = flexmock()
  57. headers = flexmock()
  58. data = {'method': 'do.stuff'}
  59. response = flexmock(ok=False)
  60. response.should_receive('raise_for_status').and_raise(
  61. module.requests.exceptions.RequestException,
  62. )
  63. flexmock(module.requests).should_receive('post').with_args(
  64. server,
  65. headers=headers,
  66. json=data,
  67. timeout=int,
  68. ).and_return(response)
  69. assert module.send_zabbix_request(server, headers, data) is None
  70. def test_send_zabbix_request_with_invalid_json_response_bails():
  71. server = flexmock()
  72. headers = flexmock()
  73. data = {'method': 'do.stuff'}
  74. flexmock(module.requests.exceptions.JSONDecodeError).should_receive('__init__')
  75. response = flexmock(ok=True)
  76. response.should_receive('json').and_raise(module.requests.exceptions.JSONDecodeError)
  77. flexmock(module.requests).should_receive('post').with_args(
  78. server,
  79. headers=headers,
  80. json=data,
  81. timeout=int,
  82. ).and_return(response)
  83. assert module.send_zabbix_request(server, headers, data) is None
  84. def test_send_zabbix_request_with_success_returns_response_result():
  85. server = flexmock()
  86. headers = flexmock()
  87. data = {'method': 'do.stuff'}
  88. response = flexmock(ok=True)
  89. response.should_receive('json').and_return({'result': {'foo': 'bar'}})
  90. flexmock(module.requests).should_receive('post').with_args(
  91. server,
  92. headers=headers,
  93. json=data,
  94. timeout=int,
  95. ).and_return(response)
  96. assert module.send_zabbix_request(server, headers, data) == {'foo': 'bar'}
  97. def test_send_zabbix_request_with_success_passes_through_missing_result():
  98. server = flexmock()
  99. headers = flexmock()
  100. data = {'method': 'do.stuff'}
  101. response = flexmock(ok=True)
  102. response.should_receive('json').and_return({})
  103. flexmock(module.requests).should_receive('post').with_args(
  104. server,
  105. headers=headers,
  106. json=data,
  107. timeout=int,
  108. ).and_return(response)
  109. assert module.send_zabbix_request(server, headers, data) is None
  110. def test_send_zabbix_request_with_error_bails():
  111. server = flexmock()
  112. headers = flexmock()
  113. data = {'method': 'do.stuff'}
  114. response = flexmock(ok=True)
  115. response.should_receive('json').and_return({'result': {'data': [{'error': 'oops'}]}})
  116. flexmock(module.requests).should_receive('post').with_args(
  117. server,
  118. headers=headers,
  119. json=data,
  120. timeout=int,
  121. ).and_return(response)
  122. assert module.send_zabbix_request(server, headers, data) is None
  123. def test_ping_monitor_with_non_matching_state_bails():
  124. hook_config = {'api_key': API_KEY}
  125. flexmock(module).should_receive('send_zabbix_request').never()
  126. module.ping_monitor(
  127. hook_config,
  128. {},
  129. 'config.yaml',
  130. borgmatic.hooks.monitoring.monitor.State.START,
  131. monitoring_log_level=1,
  132. dry_run=False,
  133. )
  134. def test_ping_monitor_config_with_api_key_only_bails():
  135. # This test should exit early since only providing an API KEY is not enough
  136. # for the hook to work
  137. hook_config = {'api_key': API_KEY}
  138. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  139. 'resolve_credential',
  140. ).replace_with(lambda value, config: value)
  141. flexmock(module.logger).should_receive('warning').once()
  142. flexmock(module).should_receive('send_zabbix_request').never()
  143. module.ping_monitor(
  144. hook_config,
  145. {},
  146. 'config.yaml',
  147. borgmatic.hooks.monitoring.monitor.State.FAIL,
  148. monitoring_log_level=1,
  149. dry_run=False,
  150. )
  151. def test_ping_monitor_config_with_host_only_bails():
  152. # This test should exit early since only providing a HOST is not enough
  153. # for the hook to work
  154. hook_config = {'host': HOST}
  155. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  156. 'resolve_credential',
  157. ).replace_with(lambda value, config: value)
  158. flexmock(module.logger).should_receive('warning').once()
  159. flexmock(module).should_receive('send_zabbix_request').never()
  160. module.ping_monitor(
  161. hook_config,
  162. {},
  163. 'config.yaml',
  164. borgmatic.hooks.monitoring.monitor.State.FAIL,
  165. monitoring_log_level=1,
  166. dry_run=False,
  167. )
  168. def test_ping_monitor_config_with_key_only_bails():
  169. # This test should exit early since only providing a KEY is not enough
  170. # for the hook to work
  171. hook_config = {'key': KEY}
  172. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  173. 'resolve_credential',
  174. ).replace_with(lambda value, config: value)
  175. flexmock(module.logger).should_receive('warning').once()
  176. flexmock(module).should_receive('send_zabbix_request').never()
  177. module.ping_monitor(
  178. hook_config,
  179. {},
  180. 'config.yaml',
  181. borgmatic.hooks.monitoring.monitor.State.FAIL,
  182. monitoring_log_level=1,
  183. dry_run=False,
  184. )
  185. def test_ping_monitor_config_with_server_only_bails():
  186. # This test should exit early since only providing a SERVER is not enough
  187. # for the hook to work
  188. hook_config = {'server': SERVER}
  189. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  190. 'resolve_credential',
  191. ).replace_with(lambda value, config: value)
  192. flexmock(module.logger).should_receive('warning').once()
  193. flexmock(module).should_receive('send_zabbix_request').never()
  194. module.ping_monitor(
  195. hook_config,
  196. {},
  197. 'config.yaml',
  198. borgmatic.hooks.monitoring.monitor.State.FAIL,
  199. monitoring_log_level=1,
  200. dry_run=False,
  201. )
  202. def test_ping_monitor_config_user_password_no_zabbix_data_bails():
  203. # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
  204. hook_config = {'server': SERVER, 'username': USERNAME, 'password': PASSWORD}
  205. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  206. 'resolve_credential',
  207. ).replace_with(lambda value, config: value)
  208. flexmock(module.logger).should_receive('warning').once()
  209. flexmock(module).should_receive('send_zabbix_request').never()
  210. module.ping_monitor(
  211. hook_config,
  212. {},
  213. 'config.yaml',
  214. borgmatic.hooks.monitoring.monitor.State.FAIL,
  215. monitoring_log_level=1,
  216. dry_run=False,
  217. )
  218. def test_ping_monitor_config_api_key_no_zabbix_data_bails():
  219. # This test should exit early since there are HOST/KEY or ITEMID provided to publish data to
  220. hook_config = {'server': SERVER, 'api_key': API_KEY}
  221. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  222. 'resolve_credential',
  223. ).replace_with(lambda value, config: value)
  224. flexmock(module.logger).should_receive('warning').once()
  225. flexmock(module).should_receive('send_zabbix_request').never()
  226. module.ping_monitor(
  227. hook_config,
  228. {},
  229. 'config.yaml',
  230. borgmatic.hooks.monitoring.monitor.State.FAIL,
  231. monitoring_log_level=1,
  232. dry_run=False,
  233. )
  234. def test_ping_monitor_config_itemid_no_auth_data_bails():
  235. # This test should exit early since there is no authentication provided
  236. # and Zabbix requires authentication to use it's API
  237. hook_config = {'server': SERVER, 'itemid': ITEMID}
  238. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  239. 'resolve_credential',
  240. ).replace_with(lambda value, config: value)
  241. flexmock(module.logger).should_receive('warning').once()
  242. flexmock(module).should_receive('send_zabbix_request').never()
  243. module.ping_monitor(
  244. hook_config,
  245. {},
  246. 'config.yaml',
  247. borgmatic.hooks.monitoring.monitor.State.FAIL,
  248. monitoring_log_level=1,
  249. dry_run=False,
  250. )
  251. def test_ping_monitor_config_host_and_key_no_auth_data_bails():
  252. # This test should exit early since there is no authentication provided
  253. # and Zabbix requires authentication to use it's API
  254. hook_config = {'server': SERVER, 'host': HOST, 'key': KEY}
  255. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  256. 'resolve_credential',
  257. ).replace_with(lambda value, config: value)
  258. flexmock(module.logger).should_receive('warning').once()
  259. flexmock(module).should_receive('send_zabbix_request').never()
  260. module.ping_monitor(
  261. hook_config,
  262. {},
  263. 'config.yaml',
  264. borgmatic.hooks.monitoring.monitor.State.FAIL,
  265. monitoring_log_level=1,
  266. dry_run=False,
  267. )
  268. def test_ping_monitor_config_host_and_key_with_api_key_auth_data_successful():
  269. # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
  270. # to authenticate and HOST/KEY to know which item to populate in Zabbix.
  271. hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
  272. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  273. 'resolve_credential',
  274. ).replace_with(lambda value, config: value)
  275. flexmock(module).should_receive('send_zabbix_request').with_args(
  276. f'{SERVER}',
  277. headers=AUTH_HEADERS,
  278. data=DATA_HOST_KEY,
  279. ).once()
  280. flexmock(module.logger).should_receive('warning').never()
  281. module.ping_monitor(
  282. hook_config,
  283. {},
  284. 'config.yaml',
  285. borgmatic.hooks.monitoring.monitor.State.FAIL,
  286. monitoring_log_level=1,
  287. dry_run=False,
  288. )
  289. def test_ping_monitor_config_adds_missing_api_endpoint_to_server_url():
  290. # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
  291. # to authenticate and HOST/KEY to know which item to populate in Zabbix.
  292. hook_config = {'server': SERVER, 'host': HOST, 'key': KEY, 'api_key': API_KEY}
  293. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  294. 'resolve_credential',
  295. ).replace_with(lambda value, config: value)
  296. flexmock(module).should_receive('send_zabbix_request').with_args(
  297. f'{SERVER}',
  298. headers=AUTH_HEADERS,
  299. data=DATA_HOST_KEY,
  300. ).once()
  301. flexmock(module.logger).should_receive('warning').never()
  302. module.ping_monitor(
  303. hook_config,
  304. {},
  305. 'config.yaml',
  306. borgmatic.hooks.monitoring.monitor.State.FAIL,
  307. monitoring_log_level=1,
  308. dry_run=False,
  309. )
  310. def test_ping_monitor_config_host_and_missing_key_bails():
  311. hook_config = {'server': SERVER, 'host': HOST, 'api_key': API_KEY}
  312. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  313. 'resolve_credential',
  314. ).replace_with(lambda value, config: value)
  315. flexmock(module.logger).should_receive('warning').once()
  316. flexmock(module).should_receive('send_zabbix_request').never()
  317. module.ping_monitor(
  318. hook_config,
  319. {},
  320. 'config.yaml',
  321. borgmatic.hooks.monitoring.monitor.State.FAIL,
  322. monitoring_log_level=1,
  323. dry_run=False,
  324. )
  325. def test_ping_monitor_config_key_and_missing_host_bails():
  326. hook_config = {'server': SERVER, 'key': KEY, 'api_key': API_KEY}
  327. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  328. 'resolve_credential',
  329. ).replace_with(lambda value, config: value)
  330. flexmock(module.logger).should_receive('warning').once()
  331. flexmock(module).should_receive('send_zabbix_request').never()
  332. module.ping_monitor(
  333. hook_config,
  334. {},
  335. 'config.yaml',
  336. borgmatic.hooks.monitoring.monitor.State.FAIL,
  337. monitoring_log_level=1,
  338. dry_run=False,
  339. )
  340. def test_ping_monitor_config_host_and_key_with_username_password_auth_data_successful():
  341. # This test should simulate a successful POST to a Zabbix server. This test uses USERNAME/PASSWORD
  342. # to authenticate and HOST/KEY to know which item to populate in Zabbix.
  343. hook_config = {
  344. 'server': SERVER,
  345. 'host': HOST,
  346. 'key': KEY,
  347. 'username': USERNAME,
  348. 'password': PASSWORD,
  349. }
  350. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  351. 'resolve_credential',
  352. ).replace_with(lambda value, config: value)
  353. flexmock(module).should_receive('send_zabbix_request').with_args(
  354. f'{SERVER}',
  355. headers=AUTH_HEADERS_LOGIN,
  356. data=DATA_USER_LOGIN,
  357. ).and_return('fakekey').once()
  358. flexmock(module.logger).should_receive('warning').never()
  359. flexmock(module).should_receive('send_zabbix_request').with_args(
  360. f'{SERVER}',
  361. headers=AUTH_HEADERS,
  362. data=DATA_HOST_KEY_WITH_KEY_VALUE,
  363. ).once()
  364. flexmock(module).should_receive('send_zabbix_request').with_args(
  365. f'{SERVER}',
  366. headers=AUTH_HEADERS,
  367. data=DATA_USER_LOGOUT,
  368. ).once()
  369. module.ping_monitor(
  370. hook_config,
  371. {},
  372. 'config.yaml',
  373. borgmatic.hooks.monitoring.monitor.State.FAIL,
  374. monitoring_log_level=1,
  375. dry_run=False,
  376. )
  377. def test_ping_monitor_config_host_and_key_with_username_password_auth_data_and_auth_post_error_bails():
  378. hook_config = {
  379. 'server': SERVER,
  380. 'host': HOST,
  381. 'key': KEY,
  382. 'username': USERNAME,
  383. 'password': PASSWORD,
  384. }
  385. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  386. 'resolve_credential',
  387. ).replace_with(lambda value, config: value)
  388. flexmock(module).should_receive('send_zabbix_request').with_args(
  389. f'{SERVER}',
  390. headers=AUTH_HEADERS_LOGIN,
  391. data=DATA_USER_LOGIN,
  392. ).and_return(None).once()
  393. flexmock(module).should_receive('send_zabbix_request').with_args(
  394. f'{SERVER}',
  395. headers=AUTH_HEADERS,
  396. data=DATA_HOST_KEY_WITH_KEY_VALUE,
  397. ).never()
  398. flexmock(module).should_receive('send_zabbix_request').with_args(
  399. f'{SERVER}',
  400. headers=AUTH_HEADERS,
  401. data=DATA_USER_LOGOUT,
  402. ).never()
  403. module.ping_monitor(
  404. hook_config,
  405. {},
  406. 'config.yaml',
  407. borgmatic.hooks.monitoring.monitor.State.FAIL,
  408. monitoring_log_level=1,
  409. dry_run=False,
  410. )
  411. def test_ping_monitor_config_host_and_key_with_username_and_missing_password_bails():
  412. hook_config = {
  413. 'server': SERVER,
  414. 'host': HOST,
  415. 'key': KEY,
  416. 'username': USERNAME,
  417. }
  418. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  419. 'resolve_credential',
  420. ).replace_with(lambda value, config: value)
  421. flexmock(module.logger).should_receive('warning').once()
  422. flexmock(module).should_receive('send_zabbix_request').never()
  423. module.ping_monitor(
  424. hook_config,
  425. {},
  426. 'config.yaml',
  427. borgmatic.hooks.monitoring.monitor.State.FAIL,
  428. monitoring_log_level=1,
  429. dry_run=False,
  430. )
  431. def test_ping_monitor_config_host_and_key_with_password_and_missing_username_bails():
  432. hook_config = {
  433. 'server': SERVER,
  434. 'host': HOST,
  435. 'key': KEY,
  436. 'password': PASSWORD,
  437. }
  438. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  439. 'resolve_credential',
  440. ).replace_with(lambda value, config: value)
  441. flexmock(module.logger).should_receive('warning').once()
  442. flexmock(module).should_receive('send_zabbix_request').never()
  443. module.ping_monitor(
  444. hook_config,
  445. {},
  446. 'config.yaml',
  447. borgmatic.hooks.monitoring.monitor.State.FAIL,
  448. monitoring_log_level=1,
  449. dry_run=False,
  450. )
  451. def test_ping_monitor_config_itemid_with_api_key_auth_data_successful():
  452. # This test should simulate a successful POST to a Zabbix server. This test uses API_KEY
  453. # to authenticate and HOST/KEY to know which item to populate in Zabbix.
  454. hook_config = {'server': SERVER, 'itemid': ITEMID, 'api_key': API_KEY}
  455. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  456. 'resolve_credential',
  457. ).replace_with(lambda value, config: value)
  458. flexmock(module).should_receive('send_zabbix_request').with_args(
  459. f'{SERVER}',
  460. headers=AUTH_HEADERS,
  461. data=DATA_ITEMID,
  462. ).once()
  463. flexmock(module.logger).should_receive('warning').never()
  464. module.ping_monitor(
  465. hook_config,
  466. {},
  467. 'config.yaml',
  468. borgmatic.hooks.monitoring.monitor.State.FAIL,
  469. monitoring_log_level=1,
  470. dry_run=False,
  471. )
  472. def test_ping_monitor_config_itemid_with_username_password_auth_data_successful():
  473. # This test should simulate a successful POST to a Zabbix server. This test uses USERNAME/PASSWORD
  474. # to authenticate and HOST/KEY to know which item to populate in Zabbix.
  475. hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
  476. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  477. 'resolve_credential',
  478. ).replace_with(lambda value, config: value)
  479. flexmock(module).should_receive('send_zabbix_request').with_args(
  480. f'{SERVER}',
  481. headers=AUTH_HEADERS_LOGIN,
  482. data=DATA_USER_LOGIN,
  483. ).and_return('fakekey').once()
  484. flexmock(module.logger).should_receive('warning').never()
  485. flexmock(module).should_receive('send_zabbix_request').with_args(
  486. f'{SERVER}',
  487. headers=AUTH_HEADERS,
  488. data=DATA_HOST_KEY_WITH_ITEMID,
  489. ).once()
  490. flexmock(module).should_receive('send_zabbix_request').with_args(
  491. f'{SERVER}',
  492. headers=AUTH_HEADERS,
  493. data=DATA_USER_LOGOUT,
  494. ).once()
  495. module.ping_monitor(
  496. hook_config,
  497. {},
  498. 'config.yaml',
  499. borgmatic.hooks.monitoring.monitor.State.FAIL,
  500. monitoring_log_level=1,
  501. dry_run=False,
  502. )
  503. def test_ping_monitor_with_credential_error_bails():
  504. hook_config = {'server': SERVER, 'itemid': ITEMID, 'username': USERNAME, 'password': PASSWORD}
  505. flexmock(module.borgmatic.hooks.credential.parse).should_receive(
  506. 'resolve_credential',
  507. ).and_raise(ValueError)
  508. flexmock(module).should_receive('send_zabbix_request').never()
  509. flexmock(module.logger).should_receive('warning').once()
  510. module.ping_monitor(
  511. hook_config,
  512. {},
  513. 'config.yaml',
  514. borgmatic.hooks.monitoring.monitor.State.FAIL,
  515. monitoring_log_level=1,
  516. dry_run=False,
  517. )