kuroko2 0.2.0

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 (338) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +15 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/images/kuroko2/avatar.png +0 -0
  6. data/app/assets/images/kuroko2/kuroko-logo-horizontal.png +0 -0
  7. data/app/assets/javascripts/kuroko2/application.js +28 -0
  8. data/app/assets/javascripts/kuroko2/bootstrap.js +2363 -0
  9. data/app/assets/javascripts/kuroko2/definition_linker.js +20 -0
  10. data/app/assets/javascripts/kuroko2/instance_linker.js +10 -0
  11. data/app/assets/javascripts/kuroko2/job_definition_stats.js +67 -0
  12. data/app/assets/javascripts/kuroko2/job_definitions.js +79 -0
  13. data/app/assets/javascripts/kuroko2/job_instances.js +88 -0
  14. data/app/assets/javascripts/kuroko2/job_timelines.js.coffee +24 -0
  15. data/app/assets/javascripts/kuroko2/narrow_job_definitions.js +30 -0
  16. data/app/assets/stylesheets/kuroko2/admin-lte.css +3402 -0
  17. data/app/assets/stylesheets/kuroko2/application.scss +86 -0
  18. data/app/assets/stylesheets/kuroko2/bootstrap.css +6799 -0
  19. data/app/assets/stylesheets/kuroko2/fonts.scss +1 -0
  20. data/app/assets/stylesheets/kuroko2/job_definitions.scss +19 -0
  21. data/app/assets/stylesheets/kuroko2/users.scss +7 -0
  22. data/app/controllers/kuroko2/api/application_controller.rb +49 -0
  23. data/app/controllers/kuroko2/api/job_instances_controller.rb +46 -0
  24. data/app/controllers/kuroko2/api/stats_controller.rb +28 -0
  25. data/app/controllers/kuroko2/application_controller.rb +43 -0
  26. data/app/controllers/kuroko2/dashboard_controller.rb +27 -0
  27. data/app/controllers/kuroko2/execution_logs_controller.rb +19 -0
  28. data/app/controllers/kuroko2/executions_controller.rb +28 -0
  29. data/app/controllers/kuroko2/job_definition_stats_controller.rb +54 -0
  30. data/app/controllers/kuroko2/job_definitions_controller.rb +100 -0
  31. data/app/controllers/kuroko2/job_instances_controller.rb +87 -0
  32. data/app/controllers/kuroko2/job_schedules_controller.rb +39 -0
  33. data/app/controllers/kuroko2/job_suspend_schedules_controller.rb +38 -0
  34. data/app/controllers/kuroko2/job_timelines_controller.rb +56 -0
  35. data/app/controllers/kuroko2/logs_controller.rb +15 -0
  36. data/app/controllers/kuroko2/sessions_controller.rb +32 -0
  37. data/app/controllers/kuroko2/stars_controller.rb +30 -0
  38. data/app/controllers/kuroko2/tokens_controller.rb +47 -0
  39. data/app/controllers/kuroko2/users_controller.rb +62 -0
  40. data/app/controllers/kuroko2/workers_controller.rb +5 -0
  41. data/app/errors/http/bad_request.rb +4 -0
  42. data/app/errors/http/forbidden.rb +4 -0
  43. data/app/errors/http/unauthorized.rb +4 -0
  44. data/app/helpers/kuroko2/application_helper.rb +8 -0
  45. data/app/helpers/kuroko2/dashboard_helper.rb +4 -0
  46. data/app/helpers/kuroko2/executions_helper.rb +4 -0
  47. data/app/helpers/kuroko2/job_definitions_helper.rb +38 -0
  48. data/app/helpers/kuroko2/job_instances_helper.rb +71 -0
  49. data/app/helpers/kuroko2/job_schedules_helper.rb +4 -0
  50. data/app/helpers/kuroko2/logs_helper.rb +4 -0
  51. data/app/helpers/kuroko2/sessions_helper.rb +4 -0
  52. data/app/helpers/kuroko2/stars_helper.rb +4 -0
  53. data/app/helpers/kuroko2/tokens_helper.rb +4 -0
  54. data/app/helpers/kuroko2/users_helper.rb +4 -0
  55. data/app/helpers/kuroko2/workers_helper.rb +4 -0
  56. data/app/jobs/kuroko2/application_job.rb +4 -0
  57. data/app/mailers/kuroko2/application_mailer.rb +6 -0
  58. data/app/mailers/kuroko2/notifications.rb +63 -0
  59. data/app/models/concerns/kuroko2/table_name_customizable.rb +16 -0
  60. data/app/models/kuroko2/admin_assignment.rb +6 -0
  61. data/app/models/kuroko2/api/application_resource.rb +10 -0
  62. data/app/models/kuroko2/api/job_instance_resource.rb +7 -0
  63. data/app/models/kuroko2/application_record.rb +5 -0
  64. data/app/models/kuroko2/execution.rb +55 -0
  65. data/app/models/kuroko2/job_definition.rb +147 -0
  66. data/app/models/kuroko2/job_definition_tag.rb +6 -0
  67. data/app/models/kuroko2/job_instance.rb +118 -0
  68. data/app/models/kuroko2/job_schedule.rb +93 -0
  69. data/app/models/kuroko2/job_suspend_schedule.rb +35 -0
  70. data/app/models/kuroko2/log.rb +3 -0
  71. data/app/models/kuroko2/memory_consumption_log.rb +49 -0
  72. data/app/models/kuroko2/memory_expectancy.rb +21 -0
  73. data/app/models/kuroko2/process_signal.rb +14 -0
  74. data/app/models/kuroko2/star.rb +6 -0
  75. data/app/models/kuroko2/tag.rb +8 -0
  76. data/app/models/kuroko2/tick.rb +12 -0
  77. data/app/models/kuroko2/token.rb +101 -0
  78. data/app/models/kuroko2/user.rb +48 -0
  79. data/app/models/kuroko2/worker.rb +12 -0
  80. data/app/views/kaminari/history/_paginator.html.slim +15 -0
  81. data/app/views/kaminari/list/_paginator.html.slim +17 -0
  82. data/app/views/kuroko2/dashboard/_taglist.html.slim +13 -0
  83. data/app/views/kuroko2/dashboard/index.html.slim +49 -0
  84. data/app/views/kuroko2/execution_logs/index.json.jbuilder +16 -0
  85. data/app/views/kuroko2/executions/index.html.slim +35 -0
  86. data/app/views/kuroko2/job_definition_stats/execution_time.json.jbuilder +13 -0
  87. data/app/views/kuroko2/job_definition_stats/index.html.slim +48 -0
  88. data/app/views/kuroko2/job_definition_stats/memory.json.jbuilder +10 -0
  89. data/app/views/kuroko2/job_definitions/_alert.html.slim +7 -0
  90. data/app/views/kuroko2/job_definitions/_form.html.slim +93 -0
  91. data/app/views/kuroko2/job_definitions/_list.html.slim +26 -0
  92. data/app/views/kuroko2/job_definitions/_search_results.html.slim +51 -0
  93. data/app/views/kuroko2/job_definitions/_taglist.html.slim +13 -0
  94. data/app/views/kuroko2/job_definitions/edit.html.slim +15 -0
  95. data/app/views/kuroko2/job_definitions/index.html.slim +11 -0
  96. data/app/views/kuroko2/job_definitions/new.html.slim +12 -0
  97. data/app/views/kuroko2/job_definitions/show.html.slim +90 -0
  98. data/app/views/kuroko2/job_instances/_instance.html.slim +42 -0
  99. data/app/views/kuroko2/job_instances/index.html.slim +58 -0
  100. data/app/views/kuroko2/job_instances/show.html.slim +19 -0
  101. data/app/views/kuroko2/job_instances/working.html.slim +37 -0
  102. data/app/views/kuroko2/job_schedules/index.html.slim +32 -0
  103. data/app/views/kuroko2/job_suspend_schedules/index.html.slim +27 -0
  104. data/app/views/kuroko2/job_timelines/dataset.json.jbuilder +11 -0
  105. data/app/views/kuroko2/job_timelines/index.html.slim +31 -0
  106. data/app/views/kuroko2/kaminari/history/_paginator.html.slim +15 -0
  107. data/app/views/kuroko2/kaminari/list/_paginator.html.slim +17 -0
  108. data/app/views/kuroko2/layouts/application.html.slim +68 -0
  109. data/app/views/kuroko2/logs/index.html.slim +33 -0
  110. data/app/views/kuroko2/notifications/executor_not_assigned.text.erb +22 -0
  111. data/app/views/kuroko2/notifications/job_failure.html.slim +21 -0
  112. data/app/views/kuroko2/notifications/job_failure.text.erb +18 -0
  113. data/app/views/kuroko2/notifications/notify_long_elapsed_time.text.erb +9 -0
  114. data/app/views/kuroko2/notifications/process_absence.text.erb +22 -0
  115. data/app/views/kuroko2/notifications/remind_failure.html.slim +21 -0
  116. data/app/views/kuroko2/notifications/remind_failure.text.erb +10 -0
  117. data/app/views/kuroko2/sessions/new.html.slim +20 -0
  118. data/app/views/kuroko2/tokens/index.html.slim +42 -0
  119. data/app/views/kuroko2/users/index.html.slim +55 -0
  120. data/app/views/kuroko2/users/show.html.slim +76 -0
  121. data/app/views/kuroko2/workers/index.html.slim +40 -0
  122. data/app/views/layouts/kuroko2/application.html.slim +72 -0
  123. data/app/views/layouts/mailer.html.erb +13 -0
  124. data/app/views/layouts/mailer.text.erb +1 -0
  125. data/bin/cleanup_old_instances.rb +9 -0
  126. data/bin/remind_failure.rb +5 -0
  127. data/config/initializers/000_kuroko2.rb +16 -0
  128. data/config/initializers/assets.rb +9 -0
  129. data/config/initializers/garage.rb +2 -0
  130. data/config/initializers/kaminari_config.rb +3 -0
  131. data/config/initializers/omniauth.rb +5 -0
  132. data/config/locales/en.yml +28 -0
  133. data/config/routes.rb +54 -0
  134. data/db/migrate/001_create_job_definitions.rb +22 -0
  135. data/db/migrate/002_create_job_instances.rb +17 -0
  136. data/db/migrate/003_create_job_schedules.rb +14 -0
  137. data/db/migrate/004_create_ticks.rb +7 -0
  138. data/db/migrate/005_create_logs.rb +13 -0
  139. data/db/migrate/006_create_tokens.rb +21 -0
  140. data/db/migrate/007_create_executions.rb +26 -0
  141. data/db/migrate/008_create_process_signals.rb +15 -0
  142. data/db/migrate/009_create_users.rb +20 -0
  143. data/db/migrate/010_create_admin_assignments.rb +12 -0
  144. data/db/migrate/011_create_stars.rb +12 -0
  145. data/db/migrate/012_create_workers.rb +15 -0
  146. data/db/migrate/018_create_job_definition_tags.rb +13 -0
  147. data/db/migrate/019_create_tags.rb +11 -0
  148. data/db/migrate/021_create_memory_expectancies.rb +17 -0
  149. data/db/migrate/025_create_job_suspend_schedules.rb +12 -0
  150. data/lib/kuroko2/command/executor.rb +62 -0
  151. data/lib/kuroko2/command/kill.rb +24 -0
  152. data/lib/kuroko2/command/monitor.rb +109 -0
  153. data/lib/kuroko2/command/shell.rb +163 -0
  154. data/lib/kuroko2/configuration.rb +19 -0
  155. data/lib/kuroko2/engine.rb +59 -0
  156. data/lib/kuroko2/execution_logger/cloud_watch_logs.rb +92 -0
  157. data/lib/kuroko2/execution_logger/void.rb +13 -0
  158. data/lib/kuroko2/execution_logger.rb +20 -0
  159. data/lib/kuroko2/memory_sampler.rb +50 -0
  160. data/lib/kuroko2/return_to_validator.rb +14 -0
  161. data/lib/kuroko2/servers/base.rb +30 -0
  162. data/lib/kuroko2/servers/command_executor.rb +27 -0
  163. data/lib/kuroko2/servers/job_scheduler.rb +25 -0
  164. data/lib/kuroko2/servers/workflow_processor.rb +25 -0
  165. data/lib/kuroko2/util/logger.rb +19 -0
  166. data/lib/kuroko2/util/rails_logger_formatter.rb +9 -0
  167. data/lib/kuroko2/version.rb +3 -0
  168. data/lib/kuroko2/workflow/assertion_error.rb +6 -0
  169. data/lib/kuroko2/workflow/engine.rb +141 -0
  170. data/lib/kuroko2/workflow/engine_error.rb +6 -0
  171. data/lib/kuroko2/workflow/node.rb +124 -0
  172. data/lib/kuroko2/workflow/notifier/concerns/chat_message_builder.rb +39 -0
  173. data/lib/kuroko2/workflow/notifier/hipchat.rb +88 -0
  174. data/lib/kuroko2/workflow/notifier/mail.rb +44 -0
  175. data/lib/kuroko2/workflow/notifier/slack.rb +121 -0
  176. data/lib/kuroko2/workflow/notifier.rb +31 -0
  177. data/lib/kuroko2/workflow/processor.rb +40 -0
  178. data/lib/kuroko2/workflow/scheduler.rb +42 -0
  179. data/lib/kuroko2/workflow/script_parser.rb +66 -0
  180. data/lib/kuroko2/workflow/shell_scanner.rb +34 -0
  181. data/lib/kuroko2/workflow/syntax_error.rb +6 -0
  182. data/lib/kuroko2/workflow/task/auto_skip_error.rb +20 -0
  183. data/lib/kuroko2/workflow/task/base.rb +32 -0
  184. data/lib/kuroko2/workflow/task/env.rb +45 -0
  185. data/lib/kuroko2/workflow/task/execute.rb +128 -0
  186. data/lib/kuroko2/workflow/task/expected_time.rb +9 -0
  187. data/lib/kuroko2/workflow/task/fork.rb +45 -0
  188. data/lib/kuroko2/workflow/task/kuroko_runner.rb +24 -0
  189. data/lib/kuroko2/workflow/task/noop.rb +13 -0
  190. data/lib/kuroko2/workflow/task/queue.rb +27 -0
  191. data/lib/kuroko2/workflow/task/rails_env.rb +24 -0
  192. data/lib/kuroko2/workflow/task/sequence.rb +13 -0
  193. data/lib/kuroko2/workflow/task/sleep.rb +29 -0
  194. data/lib/kuroko2/workflow/task/sub_process.rb +53 -0
  195. data/lib/kuroko2/workflow/task/time_base.rb +44 -0
  196. data/lib/kuroko2/workflow/task/timeout.rb +9 -0
  197. data/lib/kuroko2/workflow/task/wait.rb +143 -0
  198. data/lib/kuroko2.rb +26 -0
  199. data/lib/tasks/kuroko2_tasks.rake +4 -0
  200. data/spec/command/kill_spec.rb +20 -0
  201. data/spec/command/monitor_spec.rb +68 -0
  202. data/spec/command/shell_spec.rb +87 -0
  203. data/spec/controllers/dashboard_controller_spec.rb +5 -0
  204. data/spec/controllers/executions_controller_spec.rb +23 -0
  205. data/spec/controllers/job_definition_stats_controller_spec.rb +95 -0
  206. data/spec/controllers/job_definitions_controller_spec.rb +89 -0
  207. data/spec/controllers/job_instances_controller_spec.rb +62 -0
  208. data/spec/controllers/job_schedules_controller_spec.rb +39 -0
  209. data/spec/controllers/job_suspend_schedules_controller_spec.rb +39 -0
  210. data/spec/controllers/job_timelines_controller_spec.rb +114 -0
  211. data/spec/controllers/logs_controller_spec.rb +5 -0
  212. data/spec/controllers/sessions_controller_spec.rb +58 -0
  213. data/spec/controllers/stars_controller_spec.rb +28 -0
  214. data/spec/controllers/tokens_controller_spec.rb +5 -0
  215. data/spec/controllers/users_controller_spec.rb +51 -0
  216. data/spec/controllers/workers_controller_spec.rb +5 -0
  217. data/spec/dummy/Rakefile +6 -0
  218. data/spec/dummy/app/assets/config/manifest.js +5 -0
  219. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  220. data/spec/dummy/app/assets/javascripts/cable.js +13 -0
  221. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  222. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  223. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  224. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  225. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  226. data/spec/dummy/app/jobs/application_job.rb +2 -0
  227. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  228. data/spec/dummy/app/models/application_record.rb +3 -0
  229. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  230. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  231. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  232. data/spec/dummy/bin/bundle +3 -0
  233. data/spec/dummy/bin/rails +4 -0
  234. data/spec/dummy/bin/rake +4 -0
  235. data/spec/dummy/bin/setup +34 -0
  236. data/spec/dummy/bin/update +29 -0
  237. data/spec/dummy/config/application.rb +25 -0
  238. data/spec/dummy/config/boot.rb +3 -0
  239. data/spec/dummy/config/cable.yml +9 -0
  240. data/spec/dummy/config/database.yml +29 -0
  241. data/spec/dummy/config/environment.rb +5 -0
  242. data/spec/dummy/config/environments/development.rb +54 -0
  243. data/spec/dummy/config/environments/production.rb +86 -0
  244. data/spec/dummy/config/environments/test.rb +42 -0
  245. data/spec/dummy/config/initializers/application_controller_renderer.rb +6 -0
  246. data/spec/dummy/config/initializers/assets.rb +11 -0
  247. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  248. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  249. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  250. data/spec/dummy/config/initializers/inflections.rb +16 -0
  251. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  252. data/spec/dummy/config/initializers/new_framework_defaults.rb +23 -0
  253. data/spec/dummy/config/initializers/session_store.rb +3 -0
  254. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  255. data/spec/dummy/config/kuroko2.yml +35 -0
  256. data/spec/dummy/config/locales/en.yml +23 -0
  257. data/spec/dummy/config/puma.rb +47 -0
  258. data/spec/dummy/config/routes.rb +3 -0
  259. data/spec/dummy/config/secrets.yml +22 -0
  260. data/spec/dummy/config/spring.rb +6 -0
  261. data/spec/dummy/config.ru +5 -0
  262. data/spec/dummy/db/schema.rb +217 -0
  263. data/spec/dummy/lib/dummy_extention.rb +14 -0
  264. data/spec/dummy/lib/kuroko2/workflow/task/custom_task1.rb +13 -0
  265. data/spec/dummy/public/404.html +67 -0
  266. data/spec/dummy/public/422.html +67 -0
  267. data/spec/dummy/public/500.html +66 -0
  268. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  269. data/spec/dummy/public/apple-touch-icon.png +0 -0
  270. data/spec/dummy/public/favicon.ico +0 -0
  271. data/spec/execution_logger/cloud_watch_logs_spec.rb +95 -0
  272. data/spec/factories/execution_factory.rb +13 -0
  273. data/spec/factories/job_definition_factory.rb +21 -0
  274. data/spec/factories/job_instance_factory.rb +4 -0
  275. data/spec/factories/job_schedule_factory.rb +10 -0
  276. data/spec/factories/job_suspend_schedule_factory.rb +8 -0
  277. data/spec/factories/memory_expectancy_factory.rb +5 -0
  278. data/spec/factories/process_signal_factory.rb +4 -0
  279. data/spec/factories/star_factory.rb +4 -0
  280. data/spec/factories/tick_factory.rb +4 -0
  281. data/spec/factories/token_factory.rb +10 -0
  282. data/spec/factories/user_factory.rb +11 -0
  283. data/spec/factories/worker_factory.rb +8 -0
  284. data/spec/features/dashborad_spec.rb +82 -0
  285. data/spec/features/job_definition_spec.rb +74 -0
  286. data/spec/features/job_instance_spec.rb +94 -0
  287. data/spec/features/sign_in_and_out_spec.rb +17 -0
  288. data/spec/features/users_spec.rb +90 -0
  289. data/spec/features/workers_spec.rb +44 -0
  290. data/spec/helpers/executions_helper_spec.rb +4 -0
  291. data/spec/helpers/job_definition_helper_spec.rb +42 -0
  292. data/spec/helpers/job_schedules_helper_spec.rb +4 -0
  293. data/spec/helpers/logs_helper_spec.rb +4 -0
  294. data/spec/helpers/tokens_helper_spec.rb +4 -0
  295. data/spec/helpers/users_helper_spec.rb +4 -0
  296. data/spec/helpers/workers_helper_spec.rb +4 -0
  297. data/spec/mailers/notifications_spec.rb +54 -0
  298. data/spec/memory_sampler_spec.rb +11 -0
  299. data/spec/models/admin_assignment_spec.rb +4 -0
  300. data/spec/models/execution_spec.rb +26 -0
  301. data/spec/models/job_definition_spec.rb +163 -0
  302. data/spec/models/job_instance_spec.rb +115 -0
  303. data/spec/models/job_schedule_spec.rb +121 -0
  304. data/spec/models/job_suspend_schedule_spec.rb +32 -0
  305. data/spec/models/memory_consumption_log_spec.rb +50 -0
  306. data/spec/models/memory_expectancy_spec.rb +26 -0
  307. data/spec/models/star_spec.rb +4 -0
  308. data/spec/models/tick_spec.rb +23 -0
  309. data/spec/models/token_spec.rb +54 -0
  310. data/spec/models/user_spec.rb +17 -0
  311. data/spec/models/worker_spec.rb +5 -0
  312. data/spec/rails_helper.rb +81 -0
  313. data/spec/requests/api/job_instances_spec.rb +96 -0
  314. data/spec/requests/api/stats_spec.rb +45 -0
  315. data/spec/return_to_validator_spec.rb +28 -0
  316. data/spec/settings_spec.rb +10 -0
  317. data/spec/spec_helper.rb +49 -0
  318. data/spec/support/feature_sign_in_helper.rb +31 -0
  319. data/spec/support/sign_in_helper.rb +5 -0
  320. data/spec/support/wait_for_ajax.rb +11 -0
  321. data/spec/workflow/engine_spec.rb +241 -0
  322. data/spec/workflow/node_spec.rb +62 -0
  323. data/spec/workflow/notifier/hipchat_spec.rb +117 -0
  324. data/spec/workflow/notifier/mail_spec.rb +86 -0
  325. data/spec/workflow/notifier/slack_spec.rb +110 -0
  326. data/spec/workflow/script_parser_spec.rb +119 -0
  327. data/spec/workflow/shell_scanner_spec.rb +47 -0
  328. data/spec/workflow/task/auto_skip_error_spec.rb +35 -0
  329. data/spec/workflow/task/env_spec.rb +47 -0
  330. data/spec/workflow/task/execute_spec.rb +127 -0
  331. data/spec/workflow/task/expected_time_spec.rb +52 -0
  332. data/spec/workflow/task/fork_spec.rb +30 -0
  333. data/spec/workflow/task/queue_spec.rb +45 -0
  334. data/spec/workflow/task/rails_env_spec.rb +30 -0
  335. data/spec/workflow/task/sleep_spec.rb +22 -0
  336. data/spec/workflow/task/sub_process_spec.rb +32 -0
  337. data/spec/workflow/task/wait_spec.rb +162 -0
  338. metadata +1038 -0
@@ -0,0 +1,109 @@
1
+ require 'open3'
2
+ module Kuroko2
3
+ module Command
4
+ class Monitor
5
+ NUM_FAILURES = 15
6
+
7
+ def initialize(hostname:, worker_id:)
8
+ @hostname = hostname
9
+ @worker_id = worker_id
10
+ @counter = Hash.new(0)
11
+ @intervals = {}
12
+ end
13
+
14
+ def execute
15
+ execution_ids = Worker.on(@hostname).pluck(:execution_id).compact
16
+ executions = Execution.where(id: execution_ids, mailed_at: nil).started
17
+ executions.each do |execution|
18
+ if execution.pid
19
+ if check_process_absence(execution) && log_memory_consumption?(execution)
20
+ get_memory_consumption(execution).try do |value|
21
+ execution.log_memory_consumption(value)
22
+ end
23
+ end
24
+ else
25
+ check_assignment_delay(execution)
26
+ end
27
+ end
28
+
29
+ (@counter.keys - executions.map(&:id)).each do |removable_id|
30
+ @counter.delete(removable_id)
31
+ end
32
+ end
33
+
34
+ def counter_size
35
+ @counter.size
36
+ end
37
+
38
+ private
39
+
40
+ # @return [Boolean] true means process is exists
41
+ def check_process_absence(execution)
42
+ begin
43
+ process_num = Process.kill(0, execution.pid)
44
+ @counter.delete(execution.id)
45
+ !!process_num
46
+ rescue Errno::EPERM
47
+ true
48
+ rescue Errno::ESRCH
49
+ if Execution.exists?(execution.id)
50
+ @counter[execution.id] += 1
51
+
52
+ message = "(execution.id #{execution.id}) : PID #{execution.pid} not found, increment monitor counter to #{@counter[execution.id]}."
53
+ Kuroko2.logger.info { message }
54
+ Kuroko2.logger.info(@counter) # TODO: will remove this logging (for debug only).
55
+
56
+ if @counter[execution.id] >= NUM_FAILURES
57
+ notify_process_absence(execution)
58
+ @counter.delete(execution.id)
59
+ @intervals.delete(execution.id)
60
+ end
61
+ else
62
+ @counter.delete(execution.id)
63
+ @intervals.delete(execution.id)
64
+ end
65
+ false
66
+ end
67
+ end
68
+
69
+ def log_memory_consumption?(execution)
70
+ if @intervals[execution.id]
71
+ @intervals[execution.id].reached?(Time.now)
72
+ else # first time
73
+ @intervals[execution.id] = MemoryConsumptionLog::Interval.new(Time.now)
74
+ true
75
+ end
76
+ end
77
+
78
+ def get_memory_consumption(execution)
79
+ result = MemorySampler.get_by_pgid(execution.pid)
80
+ if result
81
+ @intervals[execution.id] = @intervals[execution.id].next
82
+ result
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ def notify_process_absence(execution)
89
+ message = "(execution #{execution.uuid}) Deliver notification mail: PID #{execution.pid} is not running."
90
+ Kuroko2.logger.info { message }
91
+ execution.job_instance.logs.warn(message)
92
+
93
+ Kuroko2::Notifications.process_absence(execution, @hostname).deliver_now
94
+ execution.touch(:mailed_at)
95
+ end
96
+
97
+ def check_assignment_delay(execution)
98
+ if execution.started_at < 1.minutes.ago
99
+ message = "(execution #{execution.uuid}) Deliver notification mail: process is not assigned to any job-executor."
100
+ Kuroko2.logger.info { message }
101
+ execution.job_instance.logs.warn(message)
102
+
103
+ Kuroko2::Notifications.executor_not_assigned(execution, @hostname).deliver_now
104
+ execution.touch(:mailed_at)
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,163 @@
1
+ require 'open3'
2
+ module Kuroko2
3
+ module Command
4
+ class Shell
5
+ MAX_OUTPUT_LENGTH = 60_000
6
+ MAX_READ_LENGTH = 1024
7
+
8
+ def initialize(hostname:, worker_id: 0, worker:, queue: Execution::DEFAULT_QUEUE)
9
+ @hostname = hostname
10
+ @worker_id = worker_id
11
+ @worker = worker
12
+ @queue = queue
13
+ end
14
+
15
+ def execute
16
+ @worker.reload
17
+ unless @worker.execution_id?
18
+ if (execution = Execution.poll(@queue))
19
+ do_execute(execution)
20
+ execution
21
+ end
22
+ end
23
+ rescue RuntimeError => e
24
+ Kuroko2.logger.error("[#{@hostname}-#{@worker_id}] #{e.message}\n" +
25
+ e.backtrace.map { |trace| "[#{@hostname}-#{@worker_id}] #{trace}" }.join("\n"))
26
+
27
+ nil
28
+ end
29
+
30
+ private
31
+
32
+ def do_execute(execution)
33
+ begin
34
+ @worker.update_column(:execution_id, execution.id)
35
+
36
+ invoke(execution)
37
+ rescue SystemCallError => e
38
+ message = "[#{@hostname}-#{@worker_id}] (uuid #{execution.uuid}) `#{execution.shell}` failed because #{e.class}: #{e.message}"
39
+ execution.token.job_instance.logs.warn(message)
40
+ Kuroko2.logger.warn(message)
41
+
42
+ output = truncate_and_escape(e.message)
43
+ execution.finish(output: output, exit_status: e.errno)
44
+ ensure
45
+ @worker.update_column(:execution_id, nil)
46
+ end
47
+ end
48
+
49
+ def invoke(execution)
50
+ command = execution.shell
51
+ env = execution.context.fetch('ENV', {})
52
+
53
+ message = "[#{@hostname}-#{@worker_id}] (uuid #{execution.uuid}) `#{command}` run with env (#{env})"
54
+ execution.token.job_instance.logs.info(message)
55
+ Kuroko2.logger.info(message)
56
+
57
+ output, status = execute_shell(command, env, execution)
58
+ output = truncate_and_escape(output)
59
+
60
+ if status.signaled?
61
+ message = "[#{@hostname}-#{@worker_id}] (uuid #{execution.uuid}) `#{command}` stopped by #{Signal.signame(status.termsig)} signal (pid #{status.pid})"
62
+ execution.token.job_instance.logs.warn(message)
63
+ Kuroko2.logger.warn(message)
64
+
65
+ execution.finish_by_signal(output: output, term_signal: status.termsig)
66
+ else
67
+ message = "[#{@hostname}-#{@worker_id}] (uuid #{execution.uuid}) `#{command}` finished with #{status.exitstatus} (pid #{status.pid})"
68
+ execution.token.job_instance.logs.info(message)
69
+ Kuroko2.logger.info(message)
70
+
71
+ execution.finish(output: output, exit_status: status.exitstatus)
72
+ end
73
+ end
74
+
75
+ def execute_shell(command, env, execution)
76
+ opts = { unsetenv_others: true, pgroup: true }
77
+ opts[:chdir] = real_path(execution.context['CHDIR']) if execution.context['CHDIR']
78
+
79
+ launched_time = execution.context['meta'].try(:[], 'launched_time').to_s
80
+ job_definition_id = execution.context['meta'].try(:[], 'job_definition_id').to_s
81
+ job_definition_name = execution.context['meta'].try(:[], 'job_definition_name').to_s
82
+ job_instance_id = execution.context['meta'].try(:[], 'job_instance_id').to_s
83
+
84
+ env.reverse_merge!(
85
+ 'HOME' => ENV['HOME'],
86
+ 'PATH' => ENV['PATH'],
87
+ 'LANG' => ENV['LANG'],
88
+ 'KUROKO2_LAUNCHED_TIME' => launched_time,
89
+ 'KUROKO2_JOB_DEFINITION_ID' => job_definition_id,
90
+ 'KUROKO2_JOB_DEFINITION_NAME' => job_definition_name,
91
+ 'KUROKO2_JOB_INSTANCE_ID' => job_instance_id,
92
+ )
93
+
94
+ execution_logger = ExecutionLogger.get_logger(
95
+ stream_name: "JOB#{sprintf("%010d", job_definition_id.to_i)}/#{execution.token.job_instance.id}",
96
+ )
97
+
98
+ temporally_path_with(env['PATH']) do
99
+ Open3.popen2e(env, command, opts) do |stdin, stdout_and_stderr, thread|
100
+ stdin.close
101
+
102
+ pid = thread.pid
103
+ execution.update_attributes(pid: pid)
104
+
105
+ reader = Thread.new do
106
+ begin
107
+ output = ''
108
+ stdout_and_stderr.each do |data|
109
+ output << data
110
+
111
+ begin
112
+ execution_logger.send_log(
113
+ {
114
+ uuid: execution.uuid,
115
+ pid: pid,
116
+ level: 'NOTICE',
117
+ message: truncate_and_escape(data.chomp),
118
+ }
119
+ )
120
+ rescue => e
121
+ Kuroko2.logger.error(
122
+ "[#{@hostname}-#{@worker_id}] #{e.message}\n " + e.backtrace.join("\n "))
123
+ end
124
+ end
125
+ rescue EOFError
126
+ # do nothing
127
+ ensure
128
+ next output
129
+ end
130
+ end
131
+
132
+ status = thread.value # wait until thread is dead
133
+ output = reader.value
134
+
135
+ [output, status]
136
+ end
137
+ end
138
+ end
139
+
140
+ def real_path(path)
141
+ path = Pathname.new(path.sub(/\/\Z/, ''))
142
+ Retryable.retryable(tries: 3, sleep: 0.5, on: [Errno::ENOENT]) do
143
+ path.realpath
144
+ end
145
+ end
146
+
147
+ def temporally_path_with(path)
148
+ original_path = ENV['PATH']
149
+
150
+ ENV['PATH'] = path
151
+ yield
152
+ ensure
153
+ ENV['PATH'] = original_path
154
+ end
155
+
156
+ def truncate_and_escape(str)
157
+ str.force_encoding('utf-8')
158
+ truncated = str.length > MAX_OUTPUT_LENGTH ? str[0...MAX_OUTPUT_LENGTH] : str
159
+ truncated.scrub.each_char.select{ |c| c.bytes.count < 4 }.join('')
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,19 @@
1
+ require 'hashie'
2
+
3
+ module Kuroko2
4
+ class Configuration
5
+ class << self
6
+ DEFAULT_CONFIG = { table_name_prefix: 'kuroko2_' }.freeze
7
+
8
+ def config
9
+ @config ||= build_config
10
+ end
11
+
12
+ def build_config
13
+ filename = Rails.root.join('config', 'kuroko2.yml')
14
+ yaml = YAML::load(ERB.new(File.read(filename)).result)
15
+ Hashie::Mash.new(DEFAULT_CONFIG.merge(yaml[Rails.env]))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ module Kuroko2
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Kuroko2
4
+
5
+ config.before_configuration do
6
+ require 'kaminari'
7
+ require 'slim'
8
+ require 'jbuilder'
9
+ require 'garage'
10
+ require 'jquery-rails'
11
+ require 'momentjs-rails'
12
+ require 'rails_bootstrap_sortable'
13
+ require 'select2-rails'
14
+ require 'font-awesome-rails'
15
+ require 'visjs/rails'
16
+ require 'dotenv-rails'
17
+ require 'weak_parameters'
18
+ end
19
+
20
+ config.autoload_paths << root.join('lib')
21
+
22
+ initializer "kuroko2.configuration" do |app|
23
+ URI.parse(Kuroko2.config.url).tap do |url|
24
+ Kuroko2.config.url_host = url.host
25
+ Kuroko2.config.url_scheme = url.scheme
26
+ Kuroko2.config.url_port = url.port
27
+ end
28
+
29
+ config.active_record.table_name_prefix = Kuroko2.config.table_name_prefix
30
+
31
+ if Kuroko2.config.custom_tasks
32
+ Kuroko2.config.custom_tasks.each do |key, klass|
33
+ unless Workflow::Node::TASK_REGISTORY.has_key?(key)
34
+ Workflow::Node.register(
35
+ key: key.to_sym,
36
+ klass: Workflow::Task.const_get(klass, false)
37
+ )
38
+ end
39
+ end
40
+ end
41
+
42
+ config.action_mailer.default_url_options = {
43
+ host: Kuroko2.config.url_host,
44
+ protocol: Kuroko2.config.url_scheme,
45
+ port: Kuroko2.config.url_port
46
+ }
47
+
48
+ config.action_mailer.delivery_method = Kuroko2.config.action_mailer.delivery_method.to_sym
49
+ config.action_mailer.smtp_settings =
50
+ Kuroko2.config.action_mailer.smtp_settings.to_h.symbolize_keys || {}
51
+
52
+ if Kuroko2.config.extentions && Kuroko2.config.extentions.controller
53
+ Kuroko2.config.extentions.controller.each do |extention|
54
+ Kuroko2::ApplicationController.include(Module.const_get(extention, false))
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,92 @@
1
+ module Kuroko2
2
+ class ExecutionLogger::CloudWatchLogs
3
+ MAX_RETRY_COUNT = 5
4
+ RETRY_ERRORS = [
5
+ Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException,
6
+ Aws::CloudWatchLogs::Errors::ThrottlingException,
7
+ Aws::CloudWatchLogs::Errors::ResourceNotFoundException,
8
+ ]
9
+
10
+ attr_reader :client
11
+
12
+ def initialize(stream_name:, group_name:, region: 'ap-northeast-1')
13
+ @client = Aws::CloudWatchLogs::Client.new(region: region)
14
+
15
+ @group_name = group_name
16
+ @stream_name = stream_name
17
+ @put_log_token = nil
18
+ @get_log_token = nil
19
+ end
20
+
21
+ def send_log(message)
22
+ put_logs([{ timestamp: timestamp_now, message: message.to_json }])
23
+ end
24
+
25
+ def put_logs(events)
26
+ exception_cb = lambda do |exception|
27
+ Kuroko2.logger.warn("#{exception.class} #{exception.message} #{events}")
28
+
29
+ case exception
30
+ when Aws::CloudWatchLogs::Errors::InvalidSequenceTokenException
31
+ old_token = @put_log_token
32
+ new_token = exception.message.match(%r{\AThe given sequenceToken is invalid. The next expected sequenceToken is:\s*(\w+)\z})[1]
33
+ if new_token
34
+ @put_log_token = new_token
35
+ Kuroko2.logger.warn("Refreshed sequenceToken from '#{old_token}' to '#{@put_log_token}'")
36
+ end
37
+ when Aws::CloudWatchLogs::Errors::ResourceNotFoundException
38
+ create_log_stream
39
+ when Aws::CloudWatchLogs::Errors::ThrottlingException
40
+ sleep(0.5)
41
+ end
42
+ end
43
+
44
+ retry_options = {
45
+ exception_cb: exception_cb,
46
+ on: RETRY_ERRORS,
47
+ tries: MAX_RETRY_COUNT,
48
+ sleep: 0,
49
+ }
50
+
51
+ Retryable.retryable(retry_options) do
52
+ response = client.put_log_events(
53
+ log_group_name: @group_name,
54
+ log_stream_name: @stream_name,
55
+ log_events: events,
56
+ sequence_token: @put_log_token,
57
+ )
58
+ @put_log_token = response.data[:next_sequence_token]
59
+
60
+ Kuroko2.logger.debug("Put logs: #{@group_name} #{@stream_name} / #{response.data}")
61
+ response
62
+ end
63
+ end
64
+
65
+ def get_logs(token = @get_log_token)
66
+ response = client.get_log_events({
67
+ log_group_name: @group_name,
68
+ log_stream_name: @stream_name,
69
+ next_token: token,
70
+ start_from_head: true,
71
+ })
72
+
73
+ @get_log_token = response.next_forward_token
74
+ response
75
+ rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
76
+ raise ExecutionLogger::NotFound
77
+ end
78
+
79
+ private
80
+
81
+ def timestamp_now
82
+ (Time.now.to_f * 1000).to_i # milliseconds
83
+ end
84
+
85
+ def create_log_stream
86
+ Kuroko2.logger.info("Create log stream: #{@group_name} #{@stream_name}")
87
+ client.create_log_stream(log_group_name: @group_name, log_stream_name: @stream_name)
88
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
89
+ warn "Log stream '#{@stream_name}' already exists"
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,13 @@
1
+ module Kuroko2
2
+ class ExecutionLogger::Void
3
+ def initialize(option = {})
4
+ end
5
+
6
+ def send_log(message)
7
+ end
8
+
9
+ def get_logs(token = nil)
10
+ raise ExecutionLogger::NotFound
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ module Kuroko2
2
+ module ExecutionLogger
3
+ class NotFound < StandardError
4
+ end
5
+
6
+ def self.get_logger(option = {})
7
+ config = Kuroko2.config.execution_logger
8
+ if config.present? && config.type.present?
9
+ logger_class = const_get(config.type, false)
10
+ if config.option.present?
11
+ logger_class.new(config.option.to_h.merge(option).symbolize_keys)
12
+ else
13
+ logger_class.new(option)
14
+ end
15
+ else
16
+ Void.new(option)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,50 @@
1
+ require 'open3'
2
+ module Kuroko2
3
+ module MemorySampler
4
+ extend self
5
+
6
+ # @param [Integer] pgid process group id
7
+ # @return [Integer] sum of memory consumptions of given process group
8
+ def get_by_pgid(pgid)
9
+ case platform
10
+ when /linux/
11
+ get_by_pgid_linux(pgid)
12
+ when /darwin/
13
+ get_by_pgid_osx(pgid)
14
+ else
15
+ raise "Unknown platform: #{platform}"
16
+ end
17
+ rescue SystemCallError
18
+ nil
19
+ end
20
+
21
+ private
22
+
23
+ # Note:
24
+ # taiki-ono@ci-slave-ruby-001:~$ ps -o pgid= -o rss=
25
+ # 22848 888
26
+ # 25848 4056
27
+ def get_by_pgid_linux(pgid)
28
+ output, _, status = Open3.capture3('ps', '-o', 'pgid=', '-o', 'rss=')
29
+ if status.success?
30
+ targets = output.split("\n").select {|line| line.split(' ').first == pgid.to_s }
31
+ calculate_sum(targets.map {|line| line.split(' ')[1] })
32
+ else
33
+ nil
34
+ end
35
+ end
36
+
37
+ def get_by_pgid_osx(pgid)
38
+ output, _, status = Open3.capture3('ps', '-o' 'rss=', '-g', pgid.to_s)
39
+ status.success? ? calculate_sum(output.split("\n")) : nil
40
+ end
41
+
42
+ def calculate_sum(rss_lines)
43
+ rss_lines.reject(&:blank?).map {|s| s.scan(/\d+/).first }.map(&:to_i).reduce(&:+)
44
+ end
45
+
46
+ def platform
47
+ RUBY_PLATFORM.downcase
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,14 @@
1
+ module Kuroko2
2
+ module ReturnToValidator
3
+ def self.valid?(return_to)
4
+ if return_to.nil?
5
+ return false
6
+ end
7
+
8
+ uri = Addressable::URI.parse(return_to)
9
+ !uri.nil? && uri.host.nil? && uri.scheme.nil? && uri.path.start_with?('/')
10
+ rescue Addressable::URI::InvalidURIError
11
+ false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'kuroko2'
2
+ require 'serverengine'
3
+
4
+ module Kuroko2
5
+ module Servers
6
+ class Base
7
+ if Rails.env.development?
8
+ ActionMailer::Base.logger = Kuroko2.logger
9
+ end
10
+
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ def run
16
+ ServerEngine.create(nil, worker, default_options.merge(@options)).run
17
+ end
18
+
19
+ private
20
+
21
+ def worker
22
+ raise NotImplementedError
23
+ end
24
+
25
+ def default_options
26
+ {}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ module Kuroko2
2
+ module Servers
3
+ class CommandExecutor < Base
4
+
5
+ private
6
+
7
+ def worker
8
+ Kuroko2::Command::Executor
9
+ end
10
+
11
+ def default_options
12
+ {
13
+ worker_type: 'process',
14
+ workers: Kuroko2::Command::Executor.num_workers,
15
+ daemonize: Rails.env.production?,
16
+ log: Rails.env.production? ?
17
+ Rails.root.join("log/command-executor.log").to_s :
18
+ $stdout,
19
+ log_level: Rails.env.production? ? :info : :debug,
20
+ pid_path: Rails.root.join('tmp/pids/command-executor.pid').to_s,
21
+ supervisor: Rails.env.production?,
22
+ worker_graceful_kill_timeout: -1,
23
+ }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ require 'kuroko2/servers/base'
2
+
3
+ module Kuroko2
4
+ module Servers
5
+ class JobScheduler < Base
6
+
7
+ private
8
+
9
+ def worker
10
+ Kuroko2::Workflow::Scheduler
11
+ end
12
+
13
+ def default_options
14
+ {
15
+ worker_type: 'embedded',
16
+ daemonize: Rails.env.production?,
17
+ log: Rails.env.production? ? Rails.root.join('log/job-scheduler.log').to_s : $stdout,
18
+ log_level: Rails.env.production? ? :info : :debug,
19
+ pid_path: Rails.root.join('tmp/pids/job-scheduler.pid').to_s,
20
+ supervisor: Rails.env.production?,
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ module Kuroko2
2
+ module Servers
3
+ class WorkflowProcessor < Base
4
+
5
+ private
6
+
7
+ def worker
8
+ Kuroko2::Workflow::Processor
9
+ end
10
+
11
+ def default_options
12
+ {
13
+ worker_type: 'embedded',
14
+ daemonize: Rails.env.production?,
15
+ log: Rails.env.production? ?
16
+ Rails.root.join("log/workflow-processor.log").to_s :
17
+ $stdout,
18
+ log_level: Rails.env.production? ? :info : :debug,
19
+ pid_path: Rails.root.join('tmp/pids/workflow-processor.pid').to_s,
20
+ supervisor: Rails.env.production?,
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Kuroko2
2
+ module Util
3
+ class Logger < ::Logger
4
+ def initialize(*args)
5
+ super
6
+
7
+ @formatter = LoggerFormatter.new
8
+ end
9
+
10
+ class LoggerFormatter < ::Logger::Formatter
11
+ def call(severity, timestamp, progname, msg)
12
+ location = caller_locations(4, 1).first
13
+
14
+ "%s %-5s - %s: %s\n" % [timestamp.iso8601, severity, location.label, msg]
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Kuroko2
2
+ module Util
3
+ class RailsLoggerFormatter < ::Logger::Formatter
4
+ def call(severity, timestamp, _, msg)
5
+ "%s %-5s: %s\n" % [timestamp.iso8601, severity, msg]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Kuroko2
2
+ VERSION = '0.2.0'
3
+ end