flapjack 1.6.0 → 2.0.0b1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -6
  3. data/.gitmodules +1 -1
  4. data/.rspec +1 -1
  5. data/.ruby-version +1 -1
  6. data/.travis.yml +12 -13
  7. data/CHANGELOG.md +2 -9
  8. data/CONTRIBUTING.md +7 -2
  9. data/Gemfile +4 -13
  10. data/LICENCE +1 -0
  11. data/README.md +8 -2
  12. data/Rakefile +2 -2
  13. data/bin/flapjack +3 -12
  14. data/build.sh +4 -2
  15. data/etc/flapjack_config.toml.example +273 -0
  16. data/features/ack_after_sched_maint.feature +18 -21
  17. data/features/cli.feature +11 -71
  18. data/features/cli_flapjack-feed-events.feature +14 -15
  19. data/features/cli_flapjack-nagios-receiver.feature +12 -41
  20. data/features/cli_flapper.feature +12 -41
  21. data/features/cli_purge.feature +5 -6
  22. data/features/cli_receive-events.feature +6 -7
  23. data/features/cli_simulate-failed-check.feature +5 -6
  24. data/features/events.feature +206 -181
  25. data/features/events_check_names.feature +4 -7
  26. data/features/notification_rules.feature +144 -223
  27. data/features/notifications.feature +65 -57
  28. data/features/rollup.feature +45 -47
  29. data/features/steps/cli_steps.rb +4 -5
  30. data/features/steps/events_steps.rb +163 -373
  31. data/features/steps/notifications_steps.rb +408 -264
  32. data/features/steps/packaging-lintian_steps.rb +0 -4
  33. data/features/steps/time_travel_steps.rb +0 -26
  34. data/features/support/daemons.rb +6 -31
  35. data/features/support/env.rb +65 -74
  36. data/flapjack.gemspec +22 -24
  37. data/lib/flapjack.rb +14 -7
  38. data/lib/flapjack/cli/flapper.rb +74 -173
  39. data/lib/flapjack/cli/maintenance.rb +278 -109
  40. data/lib/flapjack/cli/migrate.rb +950 -0
  41. data/lib/flapjack/cli/purge.rb +19 -22
  42. data/lib/flapjack/cli/receiver.rb +150 -326
  43. data/lib/flapjack/cli/server.rb +8 -235
  44. data/lib/flapjack/cli/simulate.rb +42 -57
  45. data/lib/flapjack/configuration.rb +51 -37
  46. data/lib/flapjack/coordinator.rb +138 -129
  47. data/lib/flapjack/data/acknowledgement.rb +177 -0
  48. data/lib/flapjack/data/alert.rb +97 -158
  49. data/lib/flapjack/data/check.rb +611 -0
  50. data/lib/flapjack/data/condition.rb +70 -0
  51. data/lib/flapjack/data/contact.rb +226 -456
  52. data/lib/flapjack/data/event.rb +96 -184
  53. data/lib/flapjack/data/extensions/associations.rb +59 -0
  54. data/lib/flapjack/data/extensions/short_name.rb +25 -0
  55. data/lib/flapjack/data/medium.rb +428 -0
  56. data/lib/flapjack/data/metrics.rb +194 -0
  57. data/lib/flapjack/data/notification.rb +22 -281
  58. data/lib/flapjack/data/rule.rb +473 -0
  59. data/lib/flapjack/data/scheduled_maintenance.rb +244 -0
  60. data/lib/flapjack/data/state.rb +221 -0
  61. data/lib/flapjack/data/statistic.rb +112 -0
  62. data/lib/flapjack/data/tag.rb +277 -0
  63. data/lib/flapjack/data/test_notification.rb +182 -0
  64. data/lib/flapjack/data/unscheduled_maintenance.rb +159 -0
  65. data/lib/flapjack/data/validators/id_validator.rb +20 -0
  66. data/lib/flapjack/exceptions.rb +6 -0
  67. data/lib/flapjack/filters/acknowledgement.rb +23 -16
  68. data/lib/flapjack/filters/base.rb +0 -5
  69. data/lib/flapjack/filters/delays.rb +53 -43
  70. data/lib/flapjack/filters/ok.rb +23 -14
  71. data/lib/flapjack/filters/scheduled_maintenance.rb +3 -3
  72. data/lib/flapjack/filters/unscheduled_maintenance.rb +12 -3
  73. data/lib/flapjack/gateways/aws_sns.rb +65 -49
  74. data/lib/flapjack/gateways/aws_sns/alert.text.erb +2 -2
  75. data/lib/flapjack/gateways/aws_sns/alert_subject.text.erb +2 -2
  76. data/lib/flapjack/gateways/aws_sns/rollup_subject.text.erb +1 -1
  77. data/lib/flapjack/gateways/email.rb +107 -90
  78. data/lib/flapjack/gateways/email/alert.html.erb +19 -18
  79. data/lib/flapjack/gateways/email/alert.text.erb +20 -14
  80. data/lib/flapjack/gateways/email/alert_subject.text.erb +2 -1
  81. data/lib/flapjack/gateways/email/rollup.html.erb +14 -13
  82. data/lib/flapjack/gateways/email/rollup.text.erb +13 -10
  83. data/lib/flapjack/gateways/jabber.rb +679 -671
  84. data/lib/flapjack/gateways/jabber/alert.text.erb +9 -6
  85. data/lib/flapjack/gateways/jsonapi.rb +164 -350
  86. data/lib/flapjack/gateways/jsonapi/data/join_descriptor.rb +44 -0
  87. data/lib/flapjack/gateways/jsonapi/data/method_descriptor.rb +21 -0
  88. data/lib/flapjack/gateways/jsonapi/helpers/headers.rb +63 -0
  89. data/lib/flapjack/gateways/jsonapi/helpers/miscellaneous.rb +136 -0
  90. data/lib/flapjack/gateways/jsonapi/helpers/resources.rb +227 -0
  91. data/lib/flapjack/gateways/jsonapi/helpers/serialiser.rb +313 -0
  92. data/lib/flapjack/gateways/jsonapi/helpers/swagger_docs.rb +322 -0
  93. data/lib/flapjack/gateways/jsonapi/methods/association_delete.rb +115 -0
  94. data/lib/flapjack/gateways/jsonapi/methods/association_get.rb +288 -0
  95. data/lib/flapjack/gateways/jsonapi/methods/association_patch.rb +178 -0
  96. data/lib/flapjack/gateways/jsonapi/methods/association_post.rb +116 -0
  97. data/lib/flapjack/gateways/jsonapi/methods/metrics.rb +71 -0
  98. data/lib/flapjack/gateways/jsonapi/methods/resource_delete.rb +119 -0
  99. data/lib/flapjack/gateways/jsonapi/methods/resource_get.rb +186 -0
  100. data/lib/flapjack/gateways/jsonapi/methods/resource_patch.rb +239 -0
  101. data/lib/flapjack/gateways/jsonapi/methods/resource_post.rb +197 -0
  102. data/lib/flapjack/gateways/jsonapi/middleware/array_param_fixer.rb +27 -0
  103. data/lib/flapjack/gateways/jsonapi/{rack → middleware}/json_params_parser.rb +7 -6
  104. data/lib/flapjack/gateways/jsonapi/middleware/request_timestamp.rb +18 -0
  105. data/lib/flapjack/gateways/oobetet.rb +222 -170
  106. data/lib/flapjack/gateways/pager_duty.rb +388 -0
  107. data/lib/flapjack/gateways/pager_duty/alert.text.erb +13 -0
  108. data/lib/flapjack/gateways/slack.rb +56 -48
  109. data/lib/flapjack/gateways/slack/alert.text.erb +1 -1
  110. data/lib/flapjack/gateways/slack/rollup.text.erb +1 -1
  111. data/lib/flapjack/gateways/sms_aspsms.rb +155 -0
  112. data/lib/flapjack/gateways/sms_aspsms/alert.text.erb +7 -0
  113. data/lib/flapjack/gateways/sms_aspsms/rollup.text.erb +2 -0
  114. data/lib/flapjack/gateways/sms_messagenet.rb +77 -57
  115. data/lib/flapjack/gateways/sms_messagenet/alert.text.erb +3 -2
  116. data/lib/flapjack/gateways/sms_nexmo.rb +53 -51
  117. data/lib/flapjack/gateways/sms_nexmo/alert.text.erb +2 -2
  118. data/lib/flapjack/gateways/sms_nexmo/rollup.text.erb +1 -1
  119. data/lib/flapjack/gateways/sms_twilio.rb +79 -62
  120. data/lib/flapjack/gateways/sms_twilio/alert.text.erb +3 -2
  121. data/lib/flapjack/gateways/web.rb +437 -345
  122. data/lib/flapjack/gateways/web/middleware/request_timestamp.rb +18 -0
  123. data/lib/flapjack/gateways/web/public/css/bootstrap.css +3793 -4340
  124. data/lib/flapjack/gateways/web/public/css/bootstrap.css.map +1 -0
  125. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
  126. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.svg +273 -214
  127. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  128. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
  129. data/lib/flapjack/gateways/web/public/fonts/glyphicons-halflings-regular.woff2 +0 -0
  130. data/lib/flapjack/gateways/web/public/js/bootstrap.js +1637 -1607
  131. data/lib/flapjack/gateways/web/public/js/self_stats.js +1 -2
  132. data/lib/flapjack/gateways/web/views/_pagination.html.erb +19 -0
  133. data/lib/flapjack/gateways/web/views/check.html.erb +159 -121
  134. data/lib/flapjack/gateways/web/views/checks.html.erb +82 -41
  135. data/lib/flapjack/gateways/web/views/contact.html.erb +59 -71
  136. data/lib/flapjack/gateways/web/views/contacts.html.erb +32 -8
  137. data/lib/flapjack/gateways/web/views/index.html.erb +2 -2
  138. data/lib/flapjack/gateways/web/views/{layout.erb → layout.html.erb} +7 -23
  139. data/lib/flapjack/gateways/web/views/self_stats.html.erb +32 -33
  140. data/lib/flapjack/gateways/web/views/tag.html.erb +32 -0
  141. data/lib/flapjack/gateways/web/views/tags.html.erb +51 -0
  142. data/lib/flapjack/logger.rb +34 -3
  143. data/lib/flapjack/notifier.rb +180 -112
  144. data/lib/flapjack/patches.rb +8 -63
  145. data/lib/flapjack/pikelet.rb +185 -143
  146. data/lib/flapjack/processor.rb +323 -191
  147. data/lib/flapjack/record_queue.rb +33 -0
  148. data/lib/flapjack/redis_proxy.rb +66 -0
  149. data/lib/flapjack/utility.rb +21 -15
  150. data/lib/flapjack/version.rb +2 -1
  151. data/libexec/httpbroker.go +218 -14
  152. data/libexec/oneoff.go +13 -10
  153. data/spec/lib/flapjack/configuration_spec.rb +286 -0
  154. data/spec/lib/flapjack/coordinator_spec.rb +103 -157
  155. data/spec/lib/flapjack/data/check_spec.rb +175 -0
  156. data/spec/lib/flapjack/data/contact_spec.rb +26 -349
  157. data/spec/lib/flapjack/data/event_spec.rb +76 -291
  158. data/spec/lib/flapjack/data/medium_spec.rb +19 -0
  159. data/spec/lib/flapjack/data/rule_spec.rb +43 -0
  160. data/spec/lib/flapjack/data/scheduled_maintenance_spec.rb +976 -0
  161. data/spec/lib/flapjack/data/unscheduled_maintenance_spec.rb +34 -0
  162. data/spec/lib/flapjack/gateways/aws_sns_spec.rb +111 -60
  163. data/spec/lib/flapjack/gateways/email_spec.rb +194 -161
  164. data/spec/lib/flapjack/gateways/jabber_spec.rb +961 -162
  165. data/spec/lib/flapjack/gateways/jsonapi/methods/check_links_spec.rb +155 -0
  166. data/spec/lib/flapjack/gateways/jsonapi/methods/checks_spec.rb +426 -0
  167. data/spec/lib/flapjack/gateways/jsonapi/methods/contact_links_spec.rb +217 -0
  168. data/spec/lib/flapjack/gateways/jsonapi/methods/contacts_spec.rb +425 -0
  169. data/spec/lib/flapjack/gateways/jsonapi/methods/events_spec.rb +271 -0
  170. data/spec/lib/flapjack/gateways/jsonapi/methods/media_spec.rb +257 -0
  171. data/spec/lib/flapjack/gateways/jsonapi/methods/medium_links_spec.rb +163 -0
  172. data/spec/lib/flapjack/gateways/jsonapi/methods/metrics_spec.rb +8 -0
  173. data/spec/lib/flapjack/gateways/jsonapi/methods/rule_links_spec.rb +212 -0
  174. data/spec/lib/flapjack/gateways/jsonapi/methods/rules_spec.rb +289 -0
  175. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenance_links_spec.rb +49 -0
  176. data/spec/lib/flapjack/gateways/jsonapi/methods/scheduled_maintenances_spec.rb +242 -0
  177. data/spec/lib/flapjack/gateways/jsonapi/methods/tag_links_spec.rb +274 -0
  178. data/spec/lib/flapjack/gateways/jsonapi/methods/tags_spec.rb +302 -0
  179. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenance_links_spec.rb +49 -0
  180. data/spec/lib/flapjack/gateways/jsonapi/methods/unscheduled_maintenances_spec.rb +339 -0
  181. data/spec/lib/flapjack/gateways/jsonapi_spec.rb +1 -1
  182. data/spec/lib/flapjack/gateways/oobetet_spec.rb +151 -79
  183. data/spec/lib/flapjack/gateways/pager_duty_spec.rb +353 -0
  184. data/spec/lib/flapjack/gateways/slack_spec.rb +53 -53
  185. data/spec/lib/flapjack/gateways/sms_aspsms_spec.rb +106 -0
  186. data/spec/lib/flapjack/gateways/sms_messagenet_spec.rb +111 -54
  187. data/spec/lib/flapjack/gateways/sms_nexmo_spec.rb +50 -51
  188. data/spec/lib/flapjack/gateways/sms_twilio_spec.rb +108 -48
  189. data/spec/lib/flapjack/gateways/web_spec.rb +144 -216
  190. data/spec/lib/flapjack/notifier_spec.rb +132 -1
  191. data/spec/lib/flapjack/pikelet_spec.rb +111 -50
  192. data/spec/lib/flapjack/processor_spec.rb +210 -40
  193. data/spec/lib/flapjack/redis_proxy_spec.rb +45 -0
  194. data/spec/lib/flapjack/utility_spec.rb +11 -15
  195. data/spec/service_consumers/fixture_data.rb +547 -0
  196. data/spec/service_consumers/pact_helper.rb +21 -32
  197. data/spec/service_consumers/pacts/flapjack-diner_v2.0.json +4652 -0
  198. data/spec/service_consumers/provider_states_for_flapjack-diner.rb +279 -322
  199. data/spec/service_consumers/provider_support.rb +8 -0
  200. data/spec/spec_helper.rb +34 -44
  201. data/spec/support/erb_view_helper.rb +1 -1
  202. data/spec/support/factories.rb +58 -0
  203. data/spec/support/jsonapi_helper.rb +15 -26
  204. data/spec/support/mock_logger.rb +43 -0
  205. data/spec/support/xmpp_comparable.rb +24 -0
  206. data/src/flapjack/transport_test.go +30 -1
  207. data/tasks/dump_keys.rake +82 -0
  208. data/tasks/events.rake +7 -7
  209. data/tasks/support/flapjack_config_benchmark.toml +28 -0
  210. data/tasks/support/flapjack_config_benchmark.yaml +0 -2
  211. metadata +175 -222
  212. data/Guardfile +0 -14
  213. data/etc/flapjack_config.yaml.example +0 -477
  214. data/features/cli_flapjack-populator.feature +0 -90
  215. data/features/support/silent_system.rb +0 -4
  216. data/lib/flapjack/cli/import.rb +0 -108
  217. data/lib/flapjack/data/entity.rb +0 -652
  218. data/lib/flapjack/data/entity_check.rb +0 -1044
  219. data/lib/flapjack/data/message.rb +0 -56
  220. data/lib/flapjack/data/migration.rb +0 -234
  221. data/lib/flapjack/data/notification_rule.rb +0 -425
  222. data/lib/flapjack/data/semaphore.rb +0 -44
  223. data/lib/flapjack/data/tagged.rb +0 -48
  224. data/lib/flapjack/gateways/jsonapi/check_methods.rb +0 -206
  225. data/lib/flapjack/gateways/jsonapi/check_presenter.rb +0 -221
  226. data/lib/flapjack/gateways/jsonapi/contact_methods.rb +0 -186
  227. data/lib/flapjack/gateways/jsonapi/entity_methods.rb +0 -223
  228. data/lib/flapjack/gateways/jsonapi/medium_methods.rb +0 -185
  229. data/lib/flapjack/gateways/jsonapi/metrics_methods.rb +0 -132
  230. data/lib/flapjack/gateways/jsonapi/notification_rule_methods.rb +0 -141
  231. data/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods.rb +0 -139
  232. data/lib/flapjack/gateways/jsonapi/report_methods.rb +0 -146
  233. data/lib/flapjack/gateways/pagerduty.rb +0 -318
  234. data/lib/flapjack/gateways/pagerduty/alert.text.erb +0 -10
  235. data/lib/flapjack/gateways/web/public/css/select2-bootstrap.css +0 -87
  236. data/lib/flapjack/gateways/web/public/css/select2.css +0 -615
  237. data/lib/flapjack/gateways/web/public/css/tablesort.css +0 -67
  238. data/lib/flapjack/gateways/web/public/img/select2-spinner.gif +0 -0
  239. data/lib/flapjack/gateways/web/public/img/select2.png +0 -0
  240. data/lib/flapjack/gateways/web/public/img/select2x2.png +0 -0
  241. data/lib/flapjack/gateways/web/public/js/backbone.js +0 -1581
  242. data/lib/flapjack/gateways/web/public/js/backbone.jsonapi.js +0 -322
  243. data/lib/flapjack/gateways/web/public/js/flapjack.js +0 -82
  244. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.js +0 -1640
  245. data/lib/flapjack/gateways/web/public/js/jquery.tablesorter.widgets.js +0 -1390
  246. data/lib/flapjack/gateways/web/public/js/modules/contact.js +0 -520
  247. data/lib/flapjack/gateways/web/public/js/modules/entity.js +0 -28
  248. data/lib/flapjack/gateways/web/public/js/modules/medium.js +0 -40
  249. data/lib/flapjack/gateways/web/public/js/select2.js +0 -3397
  250. data/lib/flapjack/gateways/web/public/js/tablesort.js +0 -44
  251. data/lib/flapjack/gateways/web/public/js/underscore.js +0 -1276
  252. data/lib/flapjack/gateways/web/views/edit_contacts.html.erb +0 -173
  253. data/lib/flapjack/gateways/web/views/entities.html.erb +0 -30
  254. data/lib/flapjack/gateways/web/views/entity.html.erb +0 -51
  255. data/lib/flapjack/rack_logger.rb +0 -47
  256. data/lib/flapjack/redis_pool.rb +0 -42
  257. data/spec/lib/flapjack/data/entity_check_spec.rb +0 -1418
  258. data/spec/lib/flapjack/data/entity_spec.rb +0 -872
  259. data/spec/lib/flapjack/data/message_spec.rb +0 -30
  260. data/spec/lib/flapjack/data/migration_spec.rb +0 -104
  261. data/spec/lib/flapjack/data/notification_rule_spec.rb +0 -232
  262. data/spec/lib/flapjack/data/notification_spec.rb +0 -53
  263. data/spec/lib/flapjack/data/semaphore_spec.rb +0 -24
  264. data/spec/lib/flapjack/filters/acknowledgement_spec.rb +0 -6
  265. data/spec/lib/flapjack/filters/delays_spec.rb +0 -6
  266. data/spec/lib/flapjack/filters/ok_spec.rb +0 -6
  267. data/spec/lib/flapjack/filters/scheduled_maintenance_spec.rb +0 -6
  268. data/spec/lib/flapjack/filters/unscheduled_maintenance_spec.rb +0 -6
  269. data/spec/lib/flapjack/gateways/jsonapi/check_methods_spec.rb +0 -315
  270. data/spec/lib/flapjack/gateways/jsonapi/check_presenter_spec.rb +0 -223
  271. data/spec/lib/flapjack/gateways/jsonapi/contact_methods_spec.rb +0 -131
  272. data/spec/lib/flapjack/gateways/jsonapi/entity_methods_spec.rb +0 -389
  273. data/spec/lib/flapjack/gateways/jsonapi/medium_methods_spec.rb +0 -231
  274. data/spec/lib/flapjack/gateways/jsonapi/notification_rule_methods_spec.rb +0 -169
  275. data/spec/lib/flapjack/gateways/jsonapi/pagerduty_credential_methods_spec.rb +0 -114
  276. data/spec/lib/flapjack/gateways/jsonapi/report_methods_spec.rb +0 -590
  277. data/spec/lib/flapjack/gateways/pagerduty_spec.rb +0 -249
  278. data/spec/lib/flapjack/gateways/web/views/check.html.erb_spec.rb +0 -21
  279. data/spec/lib/flapjack/gateways/web/views/contact.html.erb_spec.rb +0 -24
  280. data/spec/lib/flapjack/gateways/web/views/index.html.erb_spec.rb +0 -16
  281. data/spec/lib/flapjack/redis_pool_spec.rb +0 -29
  282. data/spec/service_consumers/pacts/flapjack-diner_v1.0.json +0 -4702
  283. data/tasks/entities.rake +0 -151
  284. data/tasks/profile.rake +0 -282
  285. data/tmp/acknowledge.rb +0 -13
  286. data/tmp/create_config_yaml.rb +0 -16
  287. data/tmp/create_event_ok.rb +0 -30
  288. data/tmp/create_event_unknown.rb +0 -30
  289. data/tmp/create_events_failure.rb +0 -34
  290. data/tmp/create_events_ok.rb +0 -32
  291. data/tmp/create_events_ok_fail_ack_ok.rb +0 -53
  292. data/tmp/create_events_ok_failure.rb +0 -41
  293. data/tmp/create_events_ok_failure_ack.rb +0 -53
  294. data/tmp/dummy_contacts.json +0 -43
  295. data/tmp/dummy_entities.json +0 -37
  296. data/tmp/generate_nagios_test_hosts.rb +0 -16
  297. data/tmp/notification_rules.rb +0 -73
  298. data/tmp/parse_config_yaml.rb +0 -7
  299. data/tmp/redis_find_spurious_unknown_states.rb +0 -52
  300. data/tmp/test_json_post.rb +0 -19
  301. data/tmp/test_notification_rules_api.rb +0 -171
@@ -0,0 +1,950 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Migration script, from v1 to v2 data
4
+ #
5
+ # Assumes no Flapjack instances running on source or destination DB. Also
6
+ # assumes an empty event queue and empty notification queues; best to run an
7
+ # instance of Flapjack v1 with the processor disabled to drain out notifications
8
+ # prior to running this.
9
+
10
+ # To see the state of the redis databases before and after, e.g. (using nodejs
11
+ # 'npm install redis-dump -g')
12
+ #
13
+ # be ruby bin/flapjack migrate to_v2 --source=redis://127.0.0.1/7 --destination=redis://127.0.0.1/8
14
+ # redis-dump -d 7 >~/Desktop/dump7.txt && redis-dump -d 8 >~/Desktop/dump8.txt
15
+ #
16
+ # Not migrated:
17
+ # current alertable/rollup status (these should reset on app start)
18
+ # notifications/alerts (see note above)
19
+
20
+ ENTITY_PATTERN_FRAGMENT = '[a-zA-Z0-9][a-zA-Z0-9\.\-_]*[a-zA-Z0-9]'
21
+ CHECK_PATTERN_FRAGMENT = '.+'
22
+ ID_PATTERN_FRAGMENT = '.+'
23
+ TAG_PATTERN_FRAGMENT = '.+'
24
+
25
+ # silence deprecation warning
26
+ require 'i18n'
27
+ I18n.config.enforce_available_locales = true
28
+
29
+ require 'redis'
30
+ require 'hiredis'
31
+ require 'ice_cube'
32
+ require 'zermelo'
33
+
34
+ require 'flapjack'
35
+ require 'flapjack/data/condition'
36
+ require 'flapjack/data/check'
37
+ require 'flapjack/data/state'
38
+ require 'flapjack/data/contact'
39
+ require 'flapjack/data/medium'
40
+ require 'flapjack/data/rule'
41
+ require 'flapjack/data/scheduled_maintenance'
42
+ require 'flapjack/data/unscheduled_maintenance'
43
+ require 'flapjack/data/tag'
44
+
45
+ require 'flapjack/utility'
46
+
47
+ module Flapjack
48
+ module CLI
49
+ class Migrate
50
+
51
+ include Flapjack::Utility
52
+
53
+ def initialize(global_options, options)
54
+ @global_options = global_options
55
+ @options = options
56
+
57
+ config = Flapjack::Configuration.new
58
+
59
+ if options[:source].nil? || options[:source].strip.empty?
60
+ config.load(global_options[:config])
61
+ config_env = config.all
62
+ end
63
+
64
+ @source_redis_options = config.for_redis
65
+ end
66
+
67
+ def to_v2
68
+ source_addr = (@options[:source].nil? || @options[:source].strip.empty?) ?
69
+ @source_redis_options : @options[:source]
70
+
71
+ @source_redis = case source_addr
72
+ when Hash
73
+ Redis.new(source_addr.merge(:driver => :hiredis))
74
+ when String
75
+ Redis.new(:url => source_addr, :driver => :hiredis)
76
+ else
77
+ exit_now! "could not understand source Redis config"
78
+ end
79
+
80
+ @source_redis_version = @source_redis.info['redis_version']
81
+
82
+ dest_addr = @options[:destination]
83
+ dest_redis = Redis.new(:url => dest_addr, :driver => :hiredis)
84
+
85
+ # Zermelo.logger = ::Logger.new('/tmp/zermelo_migrate.log')
86
+
87
+ do_rule_migration = @options[:rules]
88
+
89
+ Zermelo.redis = dest_redis
90
+
91
+ dest_db_size = Zermelo.redis.dbsize
92
+ if dest_db_size > 0
93
+ if @options[:force]
94
+ Zermelo.redis.flushdb
95
+ puts "Cleared #{@options[:destination]} db"
96
+ else
97
+ exit_now! "Destination db #{@options[:destination]} has " \
98
+ "#{dest_db_size} keys, and the --force option was not provided"
99
+ end
100
+ end
101
+
102
+ # key is old contact_id, value = new contact id
103
+ @contact_id_cache = {}
104
+
105
+ # key is entity_name:check_name, value is zermelo record
106
+ @check_name_cache = {}
107
+
108
+ # key is tag name, value is zermelo record
109
+ @tag_name_cache = {}
110
+
111
+ # cache old entity/check <-> contact linkage, use as limiting set in
112
+ # rule construction
113
+ @check_ids_by_contact_id_cache = {}
114
+
115
+ @current_entity_names = @source_redis.zrange('current_entities', 0, -1)
116
+ @current_check_names = source_keys_matching('current_checks:?*')
117
+
118
+ @time = Time.now
119
+
120
+ puts "#{Time.now.iso8601} Migrating contacts and media"
121
+ migrate_contacts_and_media
122
+
123
+ puts "#{Time.now.iso8601} Migrating checks"
124
+ migrate_checks
125
+
126
+ puts "Migrating check states & actions"
127
+ Flapjack::Data::Check.lock(
128
+ Flapjack::Data::State,
129
+ Flapjack::Data::ScheduledMaintenance,
130
+ Flapjack::Data::UnscheduledMaintenance
131
+ ) do
132
+
133
+ idx = 0
134
+ check_count = Flapjack::Data::Check.count
135
+
136
+ Flapjack::Data::Check.sort(:name).each do |check|
137
+ idx += 1
138
+ puts "#{Time.now.iso8601} #{idx}/#{check_count} #{check.name}"
139
+ migrate_states(check)
140
+ migrate_actions(check)
141
+ migrate_scheduled_maintenances(check)
142
+ migrate_unscheduled_maintenances(check)
143
+
144
+ if (idx % 500) == 0
145
+ # reopen connections -- avoid potential timeout for long-running jobs
146
+ @source_redis.quit
147
+ @source_redis = case source_addr
148
+ when Hash
149
+ Redis.new(source_addr.merge(:driver => :hiredis))
150
+ when String
151
+ Redis.new(:url => source_addr, :driver => :hiredis)
152
+ end
153
+
154
+ Zermelo.redis.quit
155
+ Zermelo.redis = Redis.new(:url => dest_addr, :driver => :hiredis)
156
+ end
157
+ end
158
+ end
159
+
160
+ if do_rule_migration
161
+ puts "#{Time.now.iso8601} Migrating contact/entity linkages"
162
+ migrate_contact_entity_linkages
163
+
164
+ puts "#{Time.now.iso8601} Migrating rules"
165
+ migrate_rules
166
+ end
167
+
168
+ rescue Exception => e
169
+ puts e.message
170
+ trace = e.backtrace.join("\n")
171
+ puts trace
172
+ # TODO output data, set exit status
173
+ exit 1
174
+ end
175
+
176
+ private
177
+
178
+ # no dependencies
179
+ def migrate_contacts_and_media
180
+ source_keys_matching('contact:?*').each do |contact_key|
181
+ contact_key =~ /\Acontact:(#{ID_PATTERN_FRAGMENT})\z/
182
+ contact_id = Regexp.last_match(1)
183
+ raise "Bad regex for '#{contact_key}'" if contact_id.nil?
184
+
185
+ contact_data = @source_redis.hgetall(contact_key).merge(:id => contact_id)
186
+
187
+ timezone = @source_redis.get("contact_tz:#{contact_id}")
188
+
189
+ media_addresses = @source_redis.hgetall("contact_media:#{contact_id}")
190
+ media_intervals = @source_redis.hgetall("contact_media_intervals:#{contact_id}")
191
+ media_rollup_thresholds = @source_redis.hgetall("contact_media_rollup_thresholds:#{contact_id}")
192
+
193
+ contact = Flapjack::Data::Contact.new(:name =>
194
+ "#{contact_data['first_name']} #{contact_data['last_name']}",
195
+ :timezone => timezone)
196
+ if contact.valid?
197
+ contact.save
198
+ else
199
+ puts "Invalid contact: #{contact.errors.full_messages.join(", ")} #{contact.inspect}"
200
+ next
201
+ end
202
+
203
+ media_addresses.each_pair do |media_type, address|
204
+ medium_data = if 'pagerduty'.eql?(media_type)
205
+ pagerduty_credentials =
206
+ @source_redis.hgetall("contact_pagerduty:#{contact_id}")
207
+
208
+ {
209
+ :transport => 'pagerduty',
210
+ :address => address,
211
+ :pagerduty_subdomain => pagerduty_credentials['subdomain'],
212
+ :pagerduty_token => pagerduty_credentials['token'],
213
+ :pagerduty_ack_duration => nil
214
+ }
215
+ else
216
+ rut = media_rollup_thresholds[media_type]
217
+ {
218
+ :transport => media_type,
219
+ :address => address,
220
+ :interval => media_intervals[media_type].to_i,
221
+ :rollup_threshold => rut.nil? ? nil : rut.to_i
222
+ }
223
+ end
224
+
225
+ medium = Flapjack::Data::Medium.new(medium_data)
226
+ if medium.valid?
227
+ medium.save
228
+ contact.media << medium
229
+ else
230
+ puts "Invalid medium: #{medium.errors.full_messages.join(", ")} #{medium.inspect}"
231
+ end
232
+ end
233
+
234
+ @contact_id_cache[contact_id] = contact.id
235
+ end
236
+ end
237
+
238
+ def migrate_checks
239
+ all_entity_names_by_id = @source_redis.hgetall('all_entity_names_by_id')
240
+
241
+ entity_tags_by_entity_name = {}
242
+
243
+ source_keys_matching('entity_tag:?*').each do |entity_tag_key|
244
+ entity_tag_key =~ /\Aentity_tag:(#{TAG_PATTERN_FRAGMENT})\z/
245
+ entity_tag = Regexp.last_match(1)
246
+ raise "Bad regex for '#{entity_tag_key}'" if entity_tag.nil?
247
+
248
+ entity_ids = @source_redis.smembers(entity_tag_key)
249
+
250
+ entity_ids.each do |entity_id|
251
+ entity_name = all_entity_names_by_id[entity_id]
252
+ raise "No entity name for id #{entity_id}" if entity_name.nil? || entity_name.empty?
253
+
254
+ entity_tags_by_entity_name[entity_name] ||= []
255
+ entity_tags_by_entity_name[entity_name] << entity_tag
256
+ end
257
+ end
258
+
259
+ source_keys_matching("all_checks:?*") do |all_checks_key|
260
+ all_checks_key =~ /\Aall_checks:(#{ENTITY_PATTERN_FRAGMENT})\z/
261
+
262
+ entity_name = Regexp.last_match(1)
263
+ raise "Bad regex for '#{all_checks_key}'" if entity_name.nil?
264
+
265
+ entity_tags = entity_tags_by_entity_name[entity_name]
266
+
267
+ all_check_names = @source_redis.zrange(all_checks_key, 0, -1).reject {|k| k.empty? }
268
+ current_check_names = @source_redis.zrange("current_checks:#{entity_name}", 0, -1)
269
+
270
+ all_check_names.each do |check_name|
271
+ check = find_check(entity_name, check_name, :create => true)
272
+ check.enabled = current_check_names.include?(check_name)
273
+ check.save
274
+ raise check.errors.full_messages.join(", ") unless check.persisted?
275
+
276
+ check.tags << find_tag("entity_#{entity_name}", :create => true)
277
+
278
+ unless entity_tags.nil? || entity_tags.empty?
279
+ tags = entity_tags.collect do |entity_tag|
280
+ find_tag("entitytag_#{entity_tag}", :create => true)
281
+ end
282
+ check.tags.add(*tags)
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+
289
+ # depends on contacts & checks
290
+ def migrate_contact_entity_linkages
291
+ all_entity_names_by_id = @source_redis.hgetall('all_entity_names_by_id')
292
+
293
+ source_keys_matching('contacts_for:?*').each do |contacts_for_key|
294
+
295
+ contacts_for_key =~ /\Acontacts_for:(#{ID_PATTERN_FRAGMENT})(?::(#{CHECK_PATTERN_FRAGMENT}))?\z/
296
+ entity_id = Regexp.last_match(1)
297
+ check_name = Regexp.last_match(2)
298
+ if entity_id.nil?
299
+ raise "Bad regex for '#{contacts_for_key}'"
300
+ end
301
+
302
+ entity_name = all_entity_names_by_id[entity_id]
303
+
304
+ contact_ids = @source_redis.smembers(contacts_for_key)
305
+ next if contact_ids.empty?
306
+
307
+ contacts = contact_ids.collect {|c_id| find_contact(c_id) }
308
+
309
+ contacts.each do |contact|
310
+ @check_ids_by_contact_id_cache[contact.id] ||= []
311
+ end
312
+
313
+ tag_for_check = proc do |en, cn, tn|
314
+ check = find_check(en, cn)
315
+ check.tags << find_tag(tn, :create => true)
316
+
317
+ contacts.each do |contact|
318
+ @check_ids_by_contact_id_cache[contact.id] << check.id
319
+ end
320
+ end
321
+
322
+ if check_name.nil?
323
+ # interested in entity, so apply to all checks for that entity
324
+ tag_name = "entity_#{entity_name}"
325
+
326
+ all_checks_for_entity = @source_redis.zrange("all_checks:#{entity_name}", 0, -1)
327
+ all_checks_for_entity.each do |cn|
328
+ next if cn.empty?
329
+ tag_for_check.call(entity_name, cn, tag_name)
330
+ end
331
+ else
332
+ # interested in check
333
+ tag_for_check.call(entity_name, check_name,
334
+ "check_#{entity_name}:#{check_name}")
335
+ end
336
+ end
337
+ end
338
+
339
+ # depends on checks
340
+ def migrate_states(check)
341
+ states_key = "#{check.name}:states"
342
+ return unless @source_redis.exists(states_key)
343
+
344
+ last_notification_types = %w(warning critical unknown recovery
345
+ acknowledgement)
346
+
347
+ ln_keys = last_notification_types.map {|t| "#{check.name}:last_#{t}_notification" }
348
+ ln_data = @source_redis.mget(*ln_keys)
349
+ last_notifications = Hash[ *(last_notification_types.map(&:to_sym).zip(ln_data).flatten(1)) ]
350
+
351
+ current_condition = nil
352
+ most_severe = nil
353
+
354
+ states_to_add = []
355
+
356
+ paginate_action(:list, 100, states_key) do |timestamp|
357
+ ect = "#{check.name}:#{timestamp}"
358
+ ts_keys = %w(state summary details count perfdata).map {|k| "#{ect}:#{k}"}
359
+ condition, summary, details, count, perfdata_json =
360
+ @source_redis.mget(*ts_keys)
361
+
362
+ current_condition = condition unless condition.nil?
363
+
364
+ timestamp_i = timestamp.to_i
365
+
366
+ state = Flapjack::Data::State.new(
367
+ :condition => current_condition,
368
+ :created_at => timestamp_i,
369
+ :updated_at => timestamp_i,
370
+ :summary => summary,
371
+ :details => details,
372
+ :perfdata_json => perfdata_json
373
+ )
374
+ raise state.errors.full_messages.join(", ") unless state.valid?
375
+
376
+ unless condition.nil?
377
+ if Flapjack::Data::Condition.healthy?(condition)
378
+ most_severe = nil
379
+ elsif Flapjack::Data::Condition.unhealthy.has_key?(condition) &&
380
+ (most_severe.nil? ||
381
+ (Flapjack::Data::Condition.unhealthy[condition] <
382
+ Flapjack::Data::Condition.unhealthy[most_severe.condition]))
383
+
384
+ most_severe = state
385
+ end
386
+
387
+ notif_state = 'ok'.eql?(condition) ? :recovery : condition.to_sym
388
+ if timestamp.to_i == last_notifications[notif_state]
389
+ check.latest_notifications << state
390
+ end
391
+ end
392
+
393
+ states_to_add << state
394
+ end
395
+
396
+ unless states_to_add.empty?
397
+ states_to_add.map(&:save)
398
+ check.states.add(*states_to_add)
399
+
400
+ unless current_condition.nil?
401
+ check.condition = current_condition
402
+ check.failing = !Flapjack::Data::Condition.healthy?(current_condition)
403
+ if check.valid?
404
+ check.save
405
+ else
406
+ puts "Invalid check: #{check.errors.full_messages.join(", ")} #{check.inspect}"
407
+ end
408
+ end
409
+
410
+ unless most_severe.nil?
411
+ check.most_severe = most_severe
412
+ end
413
+ end
414
+ end
415
+
416
+ # depends on checks, states
417
+ def migrate_actions(check)
418
+ actions_key = "#{check.name}:actions"
419
+ return unless @source_redis.exists(actions_key)
420
+
421
+ states_to_add = []
422
+
423
+ paginate_action(:hash, 100, actions_key) do |timestamp, action|
424
+ timestamp_f = timestamp.to_f
425
+ timestamp_i = timestamp.to_i
426
+
427
+ start_range = Zermelo::Filters::IndexRange.new(nil, timestamp_f, :by_score => true)
428
+ previous_state = check.states.intersect(:created_at => start_range).last
429
+ previous_cond = previous_state.nil? ? nil : previous_state.condition
430
+
431
+ state = Flapjack::Data::State.new(
432
+ :action => action,
433
+ :condition => previous_cond,
434
+ :created_at => timestamp_i,
435
+ :updated_at => timestamp_i
436
+ )
437
+
438
+ raise state.errors.full_messages.join(", ") unless state.valid?
439
+ states_to_add << state
440
+ end
441
+
442
+ return if states_to_add.empty?
443
+ states_to_add.map(&:save)
444
+ check.states.add(*states_to_add)
445
+ end
446
+
447
+ # depends on checks
448
+ def migrate_scheduled_maintenances(check)
449
+ sm_key = "#{check.name}:scheduled_maintenances"
450
+ return unless @source_redis.exists(sm_key)
451
+
452
+ sched_maints_to_add = []
453
+
454
+ paginate_action(:zset, 100, sm_key) do |timestamp, duration|
455
+ timestamp_i = timestamp.to_i
456
+ duration_i = duration.to_i
457
+
458
+ summary = @source_redis.get("#{check.name}:#{timestamp}:scheduled_maintenance:summary")
459
+
460
+ sched_maint = Flapjack::Data::ScheduledMaintenance.new(
461
+ :start_time => timestamp_i,
462
+ :end_time => timestamp_i + duration_i,
463
+ :summary => summary
464
+ )
465
+
466
+ if sched_maint.valid?
467
+ sched_maints_to_add << sched_maint
468
+ else
469
+ puts "Invalid scheduled maintenance: #{sched_maint.errors.full_messages.join(", ")} #{sched_maint.inspect}"
470
+ end
471
+ end
472
+
473
+ return if sched_maints_to_add.empty?
474
+ sched_maints_to_add.map(&:save)
475
+ check.scheduled_maintenances.add(*sched_maints_to_add)
476
+ end
477
+
478
+ # depends on checks
479
+ def migrate_unscheduled_maintenances(check)
480
+ usm_key = "#{check.name}:unscheduled_maintenances"
481
+ return unless @source_redis.exists(usm_key)
482
+
483
+ unsched_maints_to_add = []
484
+
485
+ paginate_action(:zset, 100, usm_key) do |timestamp, duration|
486
+ timestamp_i = timestamp.to_i
487
+ duration_i = duration.to_i
488
+
489
+ summary = @source_redis.get("#{check.name}:#{timestamp}:unscheduled_maintenance:summary")
490
+
491
+ unsched_maint = Flapjack::Data::UnscheduledMaintenance.new(
492
+ :start_time => timestamp_i,
493
+ :end_time => timestamp_i + duration_i,
494
+ :summary => summary
495
+ )
496
+
497
+ if unsched_maint.valid?
498
+ unsched_maints_to_add << unsched_maint
499
+ else
500
+ puts "Invalid unscheduled maintenance: #{unsched_maint.errors.full_messages.join(", ")} #{unsched_maint.inspect}"
501
+ end
502
+ end
503
+
504
+ return if unsched_maints_to_add.empty?
505
+ unsched_maints_to_add.map(&:save)
506
+ check.unscheduled_maintenances.add(*unsched_maints_to_add)
507
+ end
508
+
509
+ def field_from_rule_json(rule, field)
510
+ field_json = rule[field]
511
+ return if field_json.nil? || field_json.empty?
512
+ Flapjack.load_json(field_json)
513
+ end
514
+
515
+ # depends on contacts, media, checks
516
+ def migrate_rules
517
+ contact_counts_by_id = {}
518
+ check_counts_by_id = {}
519
+
520
+ source_keys_matching('contact_notification_rules:?*').each do |rules_key|
521
+
522
+ rules_key =~ /\Acontact_notification_rules:(#{ID_PATTERN_FRAGMENT})\z/
523
+ contact_id = Regexp.last_match(1)
524
+ raise "Bad regex for '#{rules_key}'" if contact_id.nil?
525
+
526
+ contact = find_contact(contact_id)
527
+
528
+ contact_num = contact_counts_by_id[contact.id]
529
+ if contact_num.nil?
530
+ contact_num = contact_counts_by_id.size + 1
531
+ contact_counts_by_id[contact.id] = contact_num
532
+ end
533
+
534
+ check_ids = @check_ids_by_contact_id_cache[contact.id]
535
+
536
+ Flapjack::Data::Rule.lock(
537
+ Flapjack::Data::Check,
538
+ Flapjack::Data::Tag,
539
+ Flapjack::Data::Contact,
540
+ Flapjack::Data::Medium
541
+ ) do
542
+
543
+ new_rule_ids = []
544
+
545
+ rule_ids = @source_redis.smembers(rules_key)
546
+ rule_ids.each do |rule_id|
547
+ rule_data = @source_redis.hgetall("notification_rule:#{rule_id}")
548
+
549
+ old_time_restrictions_json = rule_data['time_restrictions']
550
+
551
+ time_restrictions = if old_time_restrictions_json.nil? || old_time_restrictions_json.empty?
552
+ nil
553
+ else
554
+ old_time_restrictions = Flapjack.load_json(old_time_restrictions_json)
555
+ if old_time_restrictions.nil? || old_time_restrictions.empty?
556
+ []
557
+ elsif old_time_restrictions.is_a?(Hash)
558
+ [rule_time_restriction_to_icecube_schedule(old_time_restrictions, contact.time_zone)]
559
+ elsif old_time_restrictions.is_a?(Array)
560
+ old_time_restrictions.map {|t| rule_time_restriction_to_icecube_schedule(t, contact.time_zone)}
561
+ end
562
+ end
563
+
564
+ filter_tag_ids = Set.new
565
+
566
+ old_entities = field_from_rule_json(rule_data, 'entities')
567
+ entity_names = case old_entities
568
+ when String
569
+ Set.new([old_entities])
570
+ when Array
571
+ Set.new(old_entities)
572
+ else
573
+ nil
574
+ end
575
+
576
+ old_regex_entities = field_from_rule_json(rule_data, 'regex_entities')
577
+ entity_regexes = case old_regex_entities
578
+ when String
579
+ Set.new([Regexp.new(old_regex_entities)])
580
+ when Array
581
+ Set.new(old_regex_entities.map {|re| Regexp.new(re) })
582
+ else
583
+ nil
584
+ end
585
+
586
+ Flapjack::Data::Tag.intersect(:name => '/^manage_service_\d+_.+$/').each do |tag|
587
+ next if tag.name.nil? ||
588
+ (tag.name !~ /^manage_service_(\d+)_(.+)$/)
589
+ # eldest_service_id = Regexp.last_match(1)
590
+ service_name = Regexp.last_match(2)
591
+
592
+ next unless entity_names.include?(service_name) ||
593
+ entity_regexes.all? {|re| !re.match(service_name).nil? }
594
+ filter_tag_ids << tag.id
595
+ end
596
+
597
+
598
+ old_tags = field_from_rule_json(rule_data, 'tags')
599
+ tags = case old_tags
600
+ when String
601
+ Set.new([old_tags])
602
+ when Array
603
+ Set.new(old_tags)
604
+ else
605
+ nil
606
+ end
607
+
608
+ old_regex_tags = field_from_rule_json(rule_data, 'regex_tags')
609
+ tag_regexes = case old_regex_tags
610
+ when String
611
+ Set.new([Regexp.new(old_regex_tags)])
612
+ when Array
613
+ Set.new(old_regex_tags.map {|re| Regexp.new(re) })
614
+ else
615
+ nil
616
+ end
617
+
618
+ # start with the initial limited visibility range (overall scope)
619
+ old_tags_checks = Flapjack::Data::Check.intersect(:id => check_ids)
620
+
621
+ # then limit further by checks with names containing tag words
622
+ unless tags.nil? || tags.empty?
623
+ old_tags_checks = tags.inject(old_tags_checks) do |memo, tag|
624
+ memo.intersect(:name => /(?: |^)#{Regexp.escape(tag)}(?: |$)/)
625
+ end
626
+ end
627
+
628
+ # and by checks with names matching regexes
629
+ unless tag_regexes.nil? || tag_regexes.empty?
630
+ old_tags_checks = tag_regexes.inject(old_tags_checks) do |memo, tag_regex|
631
+ memo.intersect(:name => /#{tag_regex}/)
632
+ end
633
+ end
634
+
635
+ unless old_tags_checks.empty?
636
+ tag = Flapjack::Data::Tag.new(:name => "migrated_rule_#{rule_id}")
637
+ tag.save!
638
+ tag.checks.add_ids(*old_tags_checks.ids)
639
+ filter_tag_ids << tag.id
640
+ end
641
+
642
+ normal_rules_to_create = []
643
+ filter_rules_to_create = []
644
+
645
+ conditions_by_blackhole_and_media = {false => {},
646
+ true => {}}
647
+
648
+ Flapjack::Data::Condition.unhealthy.keys.each do |fail_state|
649
+ media_types = Flapjack.load_json(rule_data["#{fail_state}_media"])
650
+ next if media_types.nil? || media_types.empty?
651
+
652
+ media_types_str = media_types.sort.join("|")
653
+ blackhole = !!Flapjack.load_json(rule_data["#{fail_state}_blackhole"])
654
+ conditions_by_blackhole_and_media[blackhole][media_types_str] ||= []
655
+ conditions_by_blackhole_and_media[blackhole][media_types_str] << fail_state
656
+ end
657
+
658
+ # multiplier -- conditions_by_blackhole_and_media[false].size +
659
+ # conditions_by_blackhole_and_media[true].size
660
+
661
+ [false, true].each do |blackhole|
662
+ normal_rules_to_create += conditions_by_blackhole_and_media[blackhole].each_with_object([]) do |(media_types_str, fail_states), memo|
663
+ media_types = media_types_str.split('|')
664
+
665
+ rule_media_ids = contact.media.intersect(:transport => media_types).ids
666
+ # if no media to communicate by, don't save the rule
667
+ next if rule_media_ids.empty?
668
+
669
+ memo << {
670
+ :enabled => true,
671
+ :blackhole => blackhole,
672
+ :strategy => 'global', # filter rule also applies
673
+ :conditions_list => fail_states.sort.join(','),
674
+ :medium_ids => rule_media_ids
675
+ }
676
+ end
677
+ end
678
+
679
+ # multiply by number of applied time restrictions, if any
680
+ unless time_restrictions.empty?
681
+ replace_rules = time_restrictions.each_with_object([]) do |tr, memo|
682
+ memo += normal_rules_to_create.collect do |r|
683
+ r.merge(:time_restriction => tr)
684
+ end
685
+ end
686
+
687
+ normal_rules_to_create = replace_rules
688
+ end
689
+
690
+ unless filter_tag_ids.empty?
691
+ filter_data = {
692
+ :blackhole => true,
693
+ :strategy => 'no_tag'
694
+ }
695
+
696
+ filter_rules = normal_rules_to_create.each_with_object([]) do |r, memo|
697
+ memo << r.merge(filter_data) unless r[:blackhole]
698
+ end
699
+
700
+ filter_rules_to_create += filter_rules
701
+ end
702
+
703
+ new_rule_ids += normal_rules_to_create.collect do |r|
704
+ new_rule_id = SecureRandom.uuid
705
+ r[:id] = new_rule_id
706
+ medium_ids = r.delete(:medium_ids)
707
+ rule = Flapjack::Data::Rule.new(r)
708
+ rule.save!
709
+ rule.media.add_ids(*medium_ids)
710
+ new_rule_id
711
+ end
712
+
713
+ new_rule_ids += filter_rules_to_create.collect do |r|
714
+ new_rule_id = SecureRandom.uuid
715
+ r[:id] = new_rule_id
716
+ medium_ids = r.delete(:medium_ids)
717
+ rule = Flapjack::Data::Rule.new(r)
718
+ rule.save!
719
+ rule.media.add_ids(*medium_ids)
720
+ rule.tags.add_ids(*filter_tag_ids) unless filter_tag_ids.empty?
721
+ new_rule_id
722
+ end
723
+ end
724
+
725
+ contact.rules.add_ids(*new_rule_ids) unless new_rule_ids.empty?
726
+ end
727
+ end
728
+ end
729
+
730
+ # # ###########################################################################
731
+
732
+ def source_keys_matching(key_pat, &block)
733
+ if (@source_redis_version.split('.') <=> ['2', '8', '0']) == 1
734
+ @source_redis.scan_each(:match => key_pat, &block)
735
+ else
736
+ @source_redis.keys(key_pat).each {|k| block.call(k) }
737
+ end
738
+ end
739
+
740
+ def paginate_action(type, per_page, key, &block)
741
+ if ((@source_redis_version.split('.') <=> ['2', '8', '0']) == 1) && [:hash, :zset].include?(type)
742
+ case type
743
+ when :hash
744
+ @source_redis.hscan_each(key, :count => per_page, &block)
745
+ when :zset
746
+ @source_redis.zscan_each(key, :count => per_page, &block)
747
+ end
748
+ else
749
+ total = case type
750
+ when :hash
751
+ @source_redis.hlen(key)
752
+ when :list
753
+ @source_redis.llen(key)
754
+ when :zset
755
+ @source_redis.zcard(key)
756
+ end
757
+ return unless total > 0
758
+
759
+ current = 0
760
+
761
+ h_keys = @source_redis.hkeys(key) if :hash.eql?(type)
762
+
763
+ loop do
764
+ window_end = [current + per_page, total].min - 1
765
+ items = case type
766
+ when :hash
767
+ h_key_window = h_keys[current..window_end]
768
+ Hash[ *(@source_redis.hmget(key, *h_key_wind).flatten(1)) ]
769
+ when :list
770
+ @source_redis.lrange(key, current, window_end)
771
+ when :zset
772
+ Hash[ *(@source_redis.zrange(key, current, window_end, :with_scores => true).flatten(1)) ]
773
+ end
774
+
775
+ case type
776
+ when :hash, :zset
777
+ items.each_pair { |k, v| yield(k, v) }
778
+ when :list
779
+ items.each { |item| yield(item) }
780
+ end
781
+
782
+ current += items.size
783
+ break if current >= total
784
+ end
785
+ end
786
+ end
787
+
788
+
789
+ def find_contact(old_contact_id)
790
+ new_contact_id = @contact_id_cache[old_contact_id]
791
+ return if new_contact_id.nil?
792
+
793
+ contact = Flapjack::Data::Contact.find_by_id(new_contact_id)
794
+ raise "No contact found for old id '#{old_contact_id}', new id '#{new_contact_id}'" if contact.nil?
795
+
796
+ contact
797
+ end
798
+
799
+ def find_check(entity_name, check_name, opts = {})
800
+ new_check_name = "#{entity_name}:#{check_name}"
801
+ check = @check_name_cache[new_check_name]
802
+
803
+ if check.nil?
804
+ if opts[:create]
805
+ enabled = @current_entity_names.include?(entity_name) &&
806
+ @source_redis.zrange("current_checks:#{entity_name}", 0, -1).include?(check_name)
807
+ check = Flapjack::Data::Check.new(:name => new_check_name,
808
+ :enabled => enabled)
809
+ check.save
810
+ raise check.errors.full_messages.join(", ") unless check.persisted?
811
+ # puts "adding #{new_check_name} to check cache"
812
+ @check_name_cache[new_check_name] = check
813
+ else
814
+ puts "No check found for '#{new_check_name}'"
815
+ end
816
+ end
817
+
818
+ check
819
+ end
820
+
821
+ def find_tag(tag_name, opts = {})
822
+ tag = @tag_name_cache[tag_name]
823
+
824
+ if tag.nil? && opts[:create]
825
+ tag = Flapjack::Data::Tag.new(:name => tag_name)
826
+ tag.save
827
+ raise tag.errors.full_messages.join(", ") unless tag.persisted?
828
+ @tag_name_cache[tag_name] = tag
829
+ end
830
+
831
+ tag
832
+ end
833
+
834
+ # NB: ice_cube doesn't have much rule data validation, and has
835
+ # problems with infinite loops if the data can't logically match; see
836
+ # https://github.com/seejohnrun/ice_cube/issues/127 &
837
+ # https://github.com/seejohnrun/ice_cube/issues/137
838
+ # We may want to consider some sort of timeout-based check around
839
+ # anything that could fall into that.
840
+ #
841
+ # We don't want to replicate IceCube's from_hash behaviour here,
842
+ # but we do need to apply some sanity checking on the passed data.
843
+ def rule_time_restriction_to_icecube_schedule(tr, timezone, opts = {})
844
+ return if tr.nil? || !tr.is_a?(Hash) ||
845
+ timezone.nil? || !timezone.is_a?(ActiveSupport::TimeZone)
846
+ prepared_restrictions = rule_prepare_time_restriction(tr, timezone)
847
+ return if prepared_restrictions.nil?
848
+ IceCube::Schedule.from_hash(prepared_restrictions)
849
+ rescue ArgumentError => ae
850
+ if logger = opts[:logger]
851
+ logger.error "Couldn't parse rule data #{e.class}: #{e.message}"
852
+ logger.error prepared_restrictions.inspect
853
+ logger.error e.backtrace.join("\n")
854
+ end
855
+ nil
856
+ end
857
+
858
+ def rule_prepare_time_restriction(time_restriction, timezone = nil)
859
+ # this will hand back a 'deep' copy
860
+ tr = symbolize(time_restriction)
861
+
862
+ return unless (tr.has_key?(:start_time) || tr.has_key?(:start_date)) &&
863
+ (tr.has_key?(:end_time) || tr.has_key?(:end_date))
864
+
865
+ # exrules is deprecated in latest ice_cube, but may be stored in data
866
+ # serialised from earlier versions of the gem
867
+ # ( https://github.com/flapjack/flapjack/issues/715 )
868
+ tr.delete(:exrules)
869
+
870
+ parsed_time = proc {|tr, field|
871
+ if t = tr.delete(field)
872
+ t = t.dup
873
+ t = t[:time] if t.is_a?(Hash)
874
+
875
+ if t.is_a?(Time)
876
+ t
877
+ else
878
+ begin; (timezone || Time).parse(t); rescue ArgumentError; nil; end
879
+ end
880
+ else
881
+ nil
882
+ end
883
+ }
884
+
885
+ start_time = parsed_time.call(tr, :start_date) || parsed_time.call(tr, :start_time)
886
+ end_time = parsed_time.call(tr, :end_date) || parsed_time.call(tr, :end_time)
887
+
888
+ return unless start_time && end_time
889
+
890
+ tr[:start_time] = timezone ?
891
+ {:time => start_time, :zone => timezone.name} :
892
+ start_time
893
+
894
+ tr[:end_time] = timezone ?
895
+ {:time => end_time, :zone => timezone.name} :
896
+ end_time
897
+
898
+ tr[:duration] = end_time - start_time
899
+
900
+ # check that rrule types are valid IceCube rule types
901
+ return unless tr[:rrules].is_a?(Array) &&
902
+ tr[:rrules].all? {|rr| rr.is_a?(Hash)} &&
903
+ (tr[:rrules].map {|rr| rr[:rule_type]} -
904
+ ['Daily', 'Hourly', 'Minutely', 'Monthly', 'Secondly',
905
+ 'Weekly', 'Yearly']).empty?
906
+
907
+ # rewrite Weekly to IceCube::WeeklyRule, etc
908
+ tr[:rrules].each {|rrule|
909
+ rrule[:rule_type] = "IceCube::#{rrule[:rule_type]}Rule"
910
+ }
911
+
912
+ # TODO does this need to check classes for the following values?
913
+ # "validations": {
914
+ # "day": [1,2,3,4,5]
915
+ # },
916
+ # "interval": 1,
917
+ # "week_start": 0
918
+
919
+ tr
920
+ end
921
+
922
+ end
923
+ end
924
+ end
925
+
926
+ desc 'Flapjack mass data migration'
927
+ command :migrate do |migrate|
928
+
929
+ # NB: will always assume latest released 1.x version
930
+ migrate.desc 'Migrate Flapjack data from v1.6.0 to 2.0'
931
+ migrate.command :to_v2 do |to_v2|
932
+
933
+ to_v2.flag [:s, :source], :desc => 'Source Redis server URL e.g. ' \
934
+ 'redis://localhost:6379/0 (defaults to value from Flapjack configuration)'
935
+
936
+ to_v2.flag [:d, :destination], :desc => 'Destination Redis server URL ' \
937
+ 'e.g. redis://localhost:6379/1', :required => true
938
+
939
+ to_v2.switch [:rules], :desc => 'Also migrate rules and contact/entity visibility restrictions',
940
+ :default_value => false
941
+
942
+ to_v2.switch [:force], :desc => "Run even if the destination DB isn't empty (clears it first)",
943
+ :default_value => false
944
+
945
+ to_v2.action do |global_options,options,args|
946
+ migrator = Flapjack::CLI::Migrate.new(global_options, options)
947
+ migrator.to_v2
948
+ end
949
+ end
950
+ end