kuroko2 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 80baa8f9a1ca15294cc73f9f726f5584433d6acb
4
- data.tar.gz: 57be564404077de0d32b829687cc579ee925a02f
3
+ metadata.gz: d66f084f8f4eabb9f6a0c0b26dad87b4009a4f97
4
+ data.tar.gz: 0256ee9b074d99821adbd5c1a8bf3bb280c29574
5
5
  SHA512:
6
- metadata.gz: 7e657c7824576cecaa2e3cbc2a01abdad723efe66f4dd6da7fa0fe4f68f215f8f46dc3ca3ed7982bb79fb299d736d2363dda21846e604aa61e5c8df03dde934f
7
- data.tar.gz: 296957b071325388c8407d6dabeaabe85b1d6ce1498db6f0d80af9eefb4ed2c161c2e17f5592978057189751d5809b579bcfb00e84ccf040329b20594f7513b3
6
+ metadata.gz: 4412638419a0984cde3aaf825f92b95a4e65eae33f660968635440decb04362b659b06a7a8aa0b18796a3f00dd551fa09a553d92fbe195edcda806f642d1fcf2
7
+ data.tar.gz: 0cb2e9266689d1636c67b5e7eae2ef56556d1b9188b0e2e56abd3c1fff739de7c041feb3ea98a8afe3382f1d0813c8da773773125aed2d244076a693be8d8884
data/README.md CHANGED
@@ -38,6 +38,7 @@ Documentation is available at [docs/index.md](docs/index.md).
38
38
  - t8m8
39
39
  - yohfee
40
40
  - takonomura
41
+ - errm
41
42
 
42
43
  ## License
43
44
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,73 @@
1
+ class Kuroko2::ExecutionHistoriesController < Kuroko2::ApplicationController
2
+ def index
3
+ @histories = histories.page(params[:page])
4
+ end
5
+
6
+ def timeline
7
+ end
8
+
9
+ def dataset
10
+ set_period
11
+ @histories = histories.where('started_at < ?', @end_at).where('finished_at > ?', @start_at)
12
+ end
13
+
14
+ private
15
+
16
+ def query_params
17
+ params.permit(:queue, :hostname)
18
+ end
19
+
20
+ def histories
21
+ histories = Kuroko2::ExecutionHistory.ordered.includes(:job_definition, :job_instance)
22
+
23
+ queue = query_params[:queue]
24
+ histories = histories.where(queue: queue) if queue.present?
25
+
26
+ hostname = query_params[:hostname]
27
+ histories = histories.where(hostname: hostname) if hostname.present?
28
+
29
+ histories
30
+ end
31
+
32
+ def period_params
33
+ params.permit(:period, :end_at, :start_at)
34
+ end
35
+
36
+ def end_at
37
+ if period_params[:end_at].present?
38
+ begin
39
+ return period_params[:end_at].to_datetime
40
+ rescue ArgumentError
41
+ # do nothing
42
+ end
43
+ end
44
+ Time.current
45
+ end
46
+
47
+ def start_at
48
+ if period_params[:start_at].present?
49
+ begin
50
+ return period_params[:start_at].to_datetime
51
+ rescue ArgumentError
52
+ # do nothing
53
+ end
54
+ end
55
+ case period_params[:period]
56
+ when /\A(\d+)m\z/
57
+ $1.to_i.minutes.ago(@end_at)
58
+ when /\A(\d+)h\z/
59
+ $1.to_i.hours.ago(@end_at)
60
+ when /\A(\d+)d\z/
61
+ $1.to_i.days.ago(@end_at)
62
+ when /\A(\d+)w\z/
63
+ $1.to_i.weeks.ago(@end_at)
64
+ else
65
+ 1.hour.ago(@end_at)
66
+ end
67
+ end
68
+
69
+ def set_period
70
+ @end_at = end_at
71
+ @start_at = start_at
72
+ end
73
+ end
@@ -1,5 +1,22 @@
1
1
  class Kuroko2::WorkersController < Kuroko2::ApplicationController
2
+ before_action :set_worker, only: %i(update)
3
+
2
4
  def index
3
5
  @workers = Kuroko2::Worker.ordered.all
4
6
  end
7
+
8
+ def update
9
+ @worker.update_attributes(worker_params)
10
+ redirect_to workers_path
11
+ end
12
+
13
+ private
14
+
15
+ def set_worker
16
+ @worker = Kuroko2::Worker.find(params[:id])
17
+ end
18
+
19
+ def worker_params
20
+ params.permit(:suspended)
21
+ end
5
22
  end
@@ -0,0 +1,8 @@
1
+ class Kuroko2::ExecutionHistory < Kuroko2::ApplicationRecord
2
+ include Kuroko2::TableNameCustomizable
3
+
4
+ belongs_to :job_definition
5
+ belongs_to :job_instance
6
+
7
+ scope :ordered, -> { order(started_at: :desc) }
8
+ end
@@ -0,0 +1,10 @@
1
+ json.start @start_at.strftime('%Y-%m-%d %H:%M:%S')
2
+ json.end @end_at.strftime('%Y-%m-%d %H:%M:%S')
3
+ json.data do
4
+ json.array! @histories do |history|
5
+ json.id history.id
6
+ json.content "<a href='#{job_definition_job_instance_path(history.job_definition, history.job_instance)}'>##{history.job_definition.id} #{h(history.job_definition.name)}</a>"
7
+ json.start history.started_at.strftime('%Y-%m-%d %H:%M:%S')
8
+ json.end history.finished_at.strftime('%Y-%m-%d %H:%M:%S')
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ - content_for :title, 'Execution Histories'
2
+ - content_for :content_title do
3
+ <i class="fa fa-history"></i> Execution Histories
4
+ - @scope = @histories
5
+
6
+ .box#execution-histories
7
+ .box-header
8
+ .row
9
+ .col-md-9
10
+ h3.box-title Execution Histories
11
+ .col-md-3.right-button
12
+ = link_to raw('<i class="fa fa-clock-o"></i> Show Timeline'), timeline_execution_histories_path(params.permit(:queue, :hostname)), class: 'btn btn-default btn-small btn-block js-to-timeline'
13
+ - if @histories.empty?
14
+ .box-body
15
+ .text-muted.well.well-sm.no-shadow There are no execution histories yet.
16
+ - else
17
+ .box-body.table-responsive
18
+ table.table.table-hover
19
+ thead
20
+ tr
21
+ th.col-md-2 Hostname
22
+ th.col-md-1 WID
23
+ th.col-md-2 Queue
24
+ th.col-md-2 Job
25
+ th.col-md-2 Command
26
+ th.col-md-2 Started at
27
+ th.col-md-1 Elapsed Time
28
+ tbody
29
+ - for history in @histories
30
+ tr
31
+ td= history.hostname
32
+ td= history.worker_id
33
+ td= history.queue
34
+ td.no-decorate= link_to "##{history.job_definition.id} #{history.job_definition.name}", job_definition_job_instance_path(history.job_definition, history.job_instance)
35
+ td= history.shell
36
+ td= l(history.started_at, format: :short)
37
+ td= distance_of_time(history.started_at, history.finished_at)
38
+ .box-footer#pagination
39
+ = paginate @histories, theme: 'list'
@@ -0,0 +1,33 @@
1
+ - content_for :title, "Execution Timeline"
2
+ - content_for :content_title do
3
+ <i class="fa fa-clock-o"></i> Execution Timeline
4
+
5
+ .row
6
+ .col-md-12
7
+ .box
8
+ .box-header
9
+ nav class="navbar navbar-default"
10
+ div class="container-fluid"
11
+ div class="navbar-header"
12
+ a class="navbar-brand" href="#" Period
13
+ div class="" id="period-nav"
14
+ ul class="nav navbar-nav"
15
+ li class="#{params[:period] == '30m' ? 'active' : ''}"
16
+ = link_to '30 minutes', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '30m'))
17
+ li class="#{params[:period] == '1h' || !params[:period] ? 'active' : ''}"
18
+ = link_to '1 hour', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '1h'))
19
+ li class="#{params[:period] == '3h' ? 'active' : ''}"
20
+ = link_to '3 hours', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '3h'))
21
+ li class="#{params[:period] == '6h' ? 'active' : ''}"
22
+ = link_to '6 hours', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '6h'))
23
+ li class="#{params[:period] == '12h' ? 'active' : ''}"
24
+ = link_to '12 hours', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '12h'))
25
+ li class="#{params[:period] == '1d' ? 'active' : ''}"
26
+ = link_to '1 day', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '1d'))
27
+ li class="#{params[:period] == '3d' ? 'active' : ''}"
28
+ = link_to '3 days', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '3d'))
29
+ li class="#{params[:period] == '1w' ? 'active' : ''}"
30
+ = link_to '1 week', timeline_execution_histories_path(params.permit(:queue, :hostname, :end_at).merge(period: '1w'))
31
+
32
+ .box-body
33
+ div#timeline data-dataset-path="#{dataset_execution_histories_path(params.permit(:queue, :hostname, :start_at, :end_at, :period))}"
@@ -13,15 +13,18 @@
13
13
  th.col-md-1 WID
14
14
  th.col-md-2 Queue
15
15
  th.col-md-1 Status
16
- th.col-md-5 Execution
16
+ th.col-md-4 Execution
17
+ th.col-md-1 &nbsp;
17
18
  th.col-md-1 &nbsp;
18
19
  - for worker in @workers
19
20
  tr
20
- td= worker.hostname
21
+ td.no-decorate= link_to worker.hostname, execution_histories_path(hostname: worker.hostname)
21
22
  td= worker.worker_id
22
- td= worker.queue
23
+ td.no-decorate= link_to worker.queue, execution_histories_path(queue: worker.queue)
23
24
  td
24
- - if worker.working
25
+ - if worker.suspended
26
+ span.label.label-warning SUSPENDED
27
+ - elsif worker.working
25
28
  span.label.label-primary WORKING
26
29
  - else
27
30
  '&nbsp;
@@ -39,3 +42,22 @@
39
42
  role: 'button', class: 'btn btn-sm btn-default')
40
43
  - else
41
44
  '&nbsp;
45
+ td
46
+ - if !worker.suspendable
47
+ '&nbsp;
48
+ - elsif !worker.suspended
49
+ = button_to('Suspend',
50
+ worker_path(worker),
51
+ method: :patch,
52
+ params: { suspended: true },
53
+ role: 'button',
54
+ class: 'btn btn-xs btn-default',
55
+ data: { confirm: 'Continue suspending the worker?' })
56
+ - else
57
+ = button_to('Unsuspend',
58
+ worker_path(worker),
59
+ method: :patch,
60
+ params: { suspended: false },
61
+ role: 'button',
62
+ class: 'btn btn-xs btn-default',
63
+ data: { confirm: 'Continue unsuspending the worker?' })
@@ -0,0 +1,9 @@
1
+ old_histories = Kuroko2::ExecutionHistory.where('finished_at < ?', 2.weeks.ago)
2
+
3
+ count = old_histories.count
4
+
5
+ Kuroko2::ExecutionHistories.transaction do
6
+ old_histories.destroy_all
7
+ end
8
+
9
+ puts "Destroyed #{count} histories"
data/config/routes.rb CHANGED
@@ -22,7 +22,7 @@ Kuroko2::Engine.routes.draw do
22
22
  get 'page/:page', action: :index, on: :collection, as: 'paged'
23
23
  end
24
24
 
25
- resources :workers, only: :index
25
+ resources :workers, only: %i(index update)
26
26
  resources :job_instances, path: 'instances', only: %w() do
27
27
  get :working, action: :working, on: :collection
28
28
  end
@@ -32,6 +32,11 @@ Kuroko2::Engine.routes.draw do
32
32
  get :dataset, action: :dataset, on: :collection, defaults: { format: 'json' }
33
33
  end
34
34
 
35
+ resources :execution_histories, only: :index do
36
+ get :timeline, action: :timeline, on: :collection
37
+ get 'timeline/dataset', action: :dataset, on: :collection, defaults: { format: 'json' }, as: 'dataset'
38
+ end
39
+
35
40
  get '/sign_in', to: 'sessions#new', as: 'sign_in'
36
41
  delete '/sign_out', to: 'sessions#destroy', as: 'sign_out'
37
42
 
@@ -0,0 +1,6 @@
1
+ class AddSuspendedToWorkers < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :workers, :suspendable, :boolean, default: false, null: false, after: :execution_id
4
+ add_column :workers, :suspended, :boolean, default: false, null: false, after: :suspendable
5
+ end
6
+ end
@@ -0,0 +1,19 @@
1
+ class CreateExecutionHistories < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :execution_histories do |t|
4
+ t.string :hostname, limit: 180
5
+ t.integer :worker_id, limit: 1
6
+ t.string :queue, limit: 180, default: "@default", null: false
7
+ t.integer :job_definition_id, null: false
8
+ t.integer :job_instance_id, null: false
9
+ t.text :shell, null: false
10
+ t.datetime :started_at, null: false
11
+ t.datetime :finished_at, null: false
12
+ end
13
+
14
+ add_column :executions, :hostname, :string, limit: 180
15
+ add_column :executions, :worker_id, :integer, limit: 1
16
+
17
+ add_index :execution_histories, [:worker_id, :started_at], using: :btree
18
+ end
19
+ end
@@ -19,7 +19,8 @@ module Kuroko2
19
19
  elsif worker_id == (Command::Executor.num_workers - 1)
20
20
  Command::Monitor.new(hostname: @hostname, worker_id: worker_id)
21
21
  else
22
- @worker = Worker.where(hostname: @hostname, worker_id: worker_id, queue: @queue).first_or_create!
22
+ @worker = Worker.where(hostname: @hostname, worker_id: worker_id, queue: @queue).first_or_initialize
23
+ @worker.update!(suspendable: true)
23
24
  Command::Shell.new(hostname: @hostname, worker_id: worker_id, worker: @worker, queue: @queue)
24
25
  end
25
26
  end
@@ -14,6 +14,7 @@ module Kuroko2
14
14
 
15
15
  def execute
16
16
  @worker.reload
17
+ return nil if @worker.suspended?
17
18
  unless @worker.execution_id?
18
19
  if (execution = Execution.poll(@queue))
19
20
  do_execute(execution)
@@ -32,6 +33,7 @@ module Kuroko2
32
33
  def do_execute(execution)
33
34
  begin
34
35
  @worker.update_column(:execution_id, execution.id)
36
+ execution.update(hostname: @hostname, worker_id: @worker_id)
35
37
 
36
38
  invoke(execution)
37
39
  rescue SystemCallError => e
@@ -13,7 +13,7 @@ module Kuroko2
13
13
  end
14
14
 
15
15
  def finished_text
16
- "Finished to execute '#{@definition.name}'"
16
+ "Finished executing '#{@definition.name}'"
17
17
  end
18
18
 
19
19
  def launched_text
@@ -21,7 +21,7 @@ module Kuroko2
21
21
  end
22
22
 
23
23
  def back_to_normal_text
24
- "Backed to normal '#{@definition.name}'"
24
+ "'#{@definition.name}' is back to normal"
25
25
  end
26
26
 
27
27
  def retrying_text
@@ -33,7 +33,7 @@ module Kuroko2
33
33
  end
34
34
 
35
35
  def long_elapsed_time_text
36
- "The running time is longer than expected '#{@definition.name}'."
36
+ "The running time of '#{@definition.name}' is longer than expected."
37
37
  end
38
38
 
39
39
  def additional_text
@@ -61,6 +61,17 @@ module Kuroko2
61
61
  instance.logs.error(message)
62
62
  end
63
63
 
64
+ Kuroko2::ExecutionHistory.create(
65
+ hostname: execution.hostname,
66
+ worker_id: execution.worker_id,
67
+ queue: execution.queue,
68
+ job_definition: execution.job_definition,
69
+ job_instance: execution.job_instance,
70
+ shell: execution.shell,
71
+ started_at: execution.started_at,
72
+ finished_at: execution.finished_at,
73
+ )
74
+
64
75
  execution.with_lock do
65
76
  execution.destroy
66
77
  execution.success? ? :next : :failure
@@ -1,3 +1,3 @@
1
1
  module Kuroko2
2
- VERSION = '0.4.4'
2
+ VERSION = '0.4.5'
3
3
  end
@@ -62,6 +62,16 @@ module Kuroko2::Command
62
62
  end
63
63
  end
64
64
 
65
+ context 'when all workers are suspended' do
66
+ let(:worker) { create(:worker, suspended: true) }
67
+
68
+ it 'skips execution' do
69
+ is_expected.to be_nil
70
+ execution.reload
71
+ expect(execution.started_at).to be_nil
72
+ end
73
+ end
74
+
65
75
  describe 'memory expectancy calculation' do
66
76
  let(:worker) { create(:worker) }
67
77
  let(:memory_expectancy) { execution.job_definition.memory_expectancy }
@@ -0,0 +1,177 @@
1
+ require 'rails_helper'
2
+
3
+ describe Kuroko2::ExecutionHistoriesController do
4
+ routes { Kuroko2::Engine.routes }
5
+
6
+ before { sign_in }
7
+
8
+ let!(:histories) { create_list(:execution_history, 3) }
9
+
10
+ describe '#index' do
11
+ subject! { get :index }
12
+
13
+ it do
14
+ expect(response).to have_http_status(:ok)
15
+ expect(response).to render_template('index')
16
+
17
+ expect(assigns(:histories)).to match_array histories
18
+ end
19
+
20
+ context 'with valid queue' do
21
+ subject! { get :index, params: { queue: '@default' } }
22
+
23
+ it do
24
+ expect(assigns(:histories)).to match_array histories
25
+ end
26
+ end
27
+
28
+ context 'with unknown queue' do
29
+ subject! { get :index, params: { queue: 'unknown' } }
30
+
31
+ it do
32
+ expect(assigns(:histories)).to be_empty
33
+ end
34
+ end
35
+
36
+ context 'with valid hostname' do
37
+ subject! { get :index, params: { hostname: 'rspec' } }
38
+
39
+ it do
40
+ expect(assigns(:histories)).to match_array histories
41
+ end
42
+ end
43
+
44
+ context 'with unknown hostname' do
45
+ subject! { get :index, params: { hostname: 'unknown' } }
46
+
47
+ it do
48
+ expect(assigns(:histories)).to be_empty
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#timeline' do
54
+ subject! { get :timeline }
55
+
56
+ it do
57
+ expect(response).to have_http_status(:ok)
58
+ expect(response).to render_template('timeline')
59
+ end
60
+ end
61
+
62
+ describe '#dataset' do
63
+ subject! { get :dataset, xhr: true }
64
+
65
+ it do
66
+ expect(response).to have_http_status(:ok)
67
+
68
+ expect(assigns(:histories)).to match_array histories
69
+ expect(assigns(:end_at)).not_to be_nil
70
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 1.hour)
71
+ end
72
+
73
+ context 'with valid queue' do
74
+ subject! { get :dataset, xhr: true, params: { queue: '@default' } }
75
+
76
+ it do
77
+ expect(assigns(:histories)).to match_array histories
78
+ end
79
+ end
80
+
81
+ context 'with unknown queue' do
82
+ subject! { get :dataset, xhr: true, params: { queue: 'unknown' } }
83
+
84
+ it do
85
+ expect(assigns(:histories)).to be_empty
86
+ end
87
+ end
88
+
89
+ context 'with valid hostname' do
90
+ subject! { get :dataset, xhr: true, params: { hostname: 'rspec' } }
91
+
92
+ it do
93
+ expect(assigns(:histories)).to match_array histories
94
+ end
95
+ end
96
+
97
+ context 'with unknown hostname' do
98
+ subject! { get :dataset, xhr: true, params: { hostname: 'unknown' } }
99
+
100
+ it do
101
+ expect(assigns(:histories)).to be_empty
102
+ end
103
+ end
104
+
105
+ context 'with period' do
106
+ subject! { get :dataset, xhr: true, params: { period: period }}
107
+
108
+ context '30 minutes' do
109
+ let(:period) { '30m' }
110
+ it do
111
+ expect(assigns(:histories)).to match_array histories
112
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 30.minutes)
113
+ end
114
+ end
115
+ context '1 hour' do
116
+ let(:period) { '1h' }
117
+ it do
118
+ expect(assigns(:histories)).to match_array histories
119
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 1.hour)
120
+ end
121
+ end
122
+
123
+ context '1 day' do
124
+ let(:period) { '1d' }
125
+ it do
126
+ expect(assigns(:histories)).to match_array histories
127
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 1.day)
128
+ end
129
+ end
130
+
131
+ context '1 week' do
132
+ let(:period) { '1w' }
133
+ it do
134
+ expect(assigns(:histories)).to match_array histories
135
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 1.week)
136
+ end
137
+ end
138
+ end
139
+
140
+ context 'with end_at' do
141
+ let(:end_at) { Time.current + 5.minute }
142
+ subject! { get :dataset, xhr: true, params: { end_at: end_at } }
143
+
144
+ it do
145
+ expect(assigns(:histories)).to match_array histories
146
+ expect(assigns(:end_at).strftime("%d-%m-%Y %H:%M:%S")).to eq end_at.strftime("%d-%m-%Y %H:%M:%S")
147
+ end
148
+
149
+ context 'with invalid' do
150
+ let(:end_at) { 'invalid' }
151
+
152
+ it do
153
+ expect(assigns(:histories)).to match_array histories
154
+ end
155
+ end
156
+ end
157
+
158
+ context 'with start_at' do
159
+ let(:start_at) { 1.hour.ago(Time.current) }
160
+ subject! { get :dataset, xhr: true, params: { start_at: start_at } }
161
+
162
+ it do
163
+ expect(assigns(:histories)).to match_array histories
164
+ expect(assigns(:start_at).strftime("%d-%m-%Y %H:%M:%S")).to eq start_at.strftime("%d-%m-%Y %H:%M:%S")
165
+ end
166
+
167
+ context 'with invalid' do
168
+ let(:start_at) { 'invalid' }
169
+
170
+ it do
171
+ expect(assigns(:histories)).to match_array histories
172
+ expect(assigns(:start_at)).to eq(assigns(:end_at) - 1.hour)
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -2,4 +2,18 @@ require 'rails_helper'
2
2
 
3
3
  RSpec.describe Kuroko2::WorkersController, :type => :controller do
4
4
  routes { Kuroko2::Engine.routes }
5
+
6
+ before { sign_in }
7
+
8
+ let(:worker) { create(:worker) }
9
+
10
+ describe '#update' do
11
+ subject! { patch :update, params: { id: worker.id, suspended: true } }
12
+
13
+ it do
14
+ expect(response).to redirect_to(workers_path)
15
+
16
+ expect(assigns(:worker).suspended).to be_truthy
17
+ end
18
+ end
5
19
  end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 30) do
13
+ ActiveRecord::Schema.define(version: 31) do
14
14
 
15
15
  create_table "admin_assignments", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
16
16
  t.integer "user_id", null: false
@@ -20,6 +20,18 @@ ActiveRecord::Schema.define(version: 30) do
20
20
  t.index ["user_id", "job_definition_id"], name: "user_id", unique: true
21
21
  end
22
22
 
23
+ create_table "execution_histories", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
24
+ t.string "hostname", limit: 180
25
+ t.integer "worker_id", limit: 1
26
+ t.string "queue", limit: 180, default: "@default", null: false
27
+ t.integer "job_definition_id", null: false
28
+ t.integer "job_instance_id", null: false
29
+ t.text "shell", null: false
30
+ t.datetime "started_at", null: false
31
+ t.datetime "finished_at", null: false
32
+ t.index ["worker_id", "started_at"], name: "index_kuroko2_execution_histories_on_worker_id_and_started_at"
33
+ end
34
+
23
35
  create_table "executions", id: :integer, force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" do |t|
24
36
  t.string "uuid", limit: 36, null: false
25
37
  t.integer "job_definition_id"
@@ -38,6 +50,8 @@ ActiveRecord::Schema.define(version: 30) do
38
50
  t.datetime "mailed_at"
39
51
  t.datetime "created_at"
40
52
  t.datetime "updated_at"
53
+ t.string "hostname", limit: 180
54
+ t.integer "worker_id", limit: 1
41
55
  t.index ["job_definition_id", "token_id"], name: "index_kuroko2_executions_on_job_definition_id_and_token_id", unique: true
42
56
  t.index ["started_at"], name: "started_at"
43
57
  end
@@ -196,6 +210,8 @@ ActiveRecord::Schema.define(version: 30) do
196
210
  t.string "queue", limit: 180, default: "@default", null: false
197
211
  t.boolean "working", default: false, null: false
198
212
  t.integer "execution_id"
213
+ t.boolean "suspendable", default: false, null: false
214
+ t.boolean "suspended", default: false, null: false
199
215
  t.datetime "created_at"
200
216
  t.datetime "updated_at"
201
217
  t.index ["hostname", "worker_id"], name: "hostname", unique: true
@@ -0,0 +1,15 @@
1
+ FactoryGirl.define do
2
+ factory :execution_history, class: Kuroko2::ExecutionHistory do
3
+ hostname 'rspec'
4
+ worker_id 1
5
+ queue '@default'
6
+
7
+ job_definition { create(:job_definition) }
8
+ job_instance { create(:job_instance, job_definition: job_definition) }
9
+
10
+ shell 'echo $NAME'
11
+
12
+ started_at { Time.current }
13
+ finished_at { Time.current }
14
+ end
15
+ end
@@ -4,5 +4,6 @@ FactoryGirl.define do
4
4
  sequence(:worker_id)
5
5
  queue "@default"
6
6
  working true
7
+ suspendable true
7
8
  end
8
9
  end
@@ -0,0 +1,36 @@
1
+ require 'rails_helper'
2
+
3
+ describe 'Shows execution histories', type: :feature do
4
+ before { sign_in }
5
+
6
+ let!(:worker) { create(:worker, hostname: 'rspec') }
7
+ let(:job_definition) { create(:job_definition) }
8
+ let!(:histories) { create_list(:execution_history, 3, job_definition: job_definition) }
9
+
10
+ it 'shows list of execution histories' do
11
+ visit kuroko2.workers_path
12
+ expect(page).to have_content('Kuroko Workers')
13
+ expect(page).to have_content('rspec')
14
+ expect(page).to have_content('@default')
15
+
16
+ click_on 'rspec'
17
+
18
+ expect(page).to have_content('Execution Histories')
19
+ expect(page).to have_selector('#execution-histories table tbody tr', count: 3)
20
+ expect(page).to have_content('rspec')
21
+ expect(page).to have_content('@default')
22
+ expect(page).to have_content(job_definition.name)
23
+ end
24
+
25
+ it 'shows timeline of execution histories' do
26
+ visit kuroko2.execution_histories_path(hostname: 'rspec')
27
+
28
+ expect(page).to have_content('Execution Histories')
29
+ expect(page).to have_content('Show Timeline')
30
+
31
+ click_on 'Show Timeline'
32
+
33
+ expect(page).to have_content('Execution Timeline')
34
+ expect(page).to have_content(job_definition.name)
35
+ end
36
+ end
@@ -44,4 +44,37 @@ RSpec.describe "Show list of workers", type: :feature do
44
44
  expect(page).not_to have_content('echo Hello!')
45
45
  expect(page).to have_selector('#workers table tbody tr td .btn', text: 'Details', count: 0)
46
46
  end
47
+
48
+ context 'toggle suspended' do
49
+ it js: true do
50
+ visit kuroko2.workers_path
51
+ expect(page).to have_selector('#workers table tbody tr', count: 2)
52
+ expect(page).not_to have_content('echo Hello!')
53
+ expect(page).to have_title('Kuroko Workers « Kuroko 2')
54
+ expect(page).to have_selector('i.fa.fa-rocket', text: '')
55
+ have_selector('h1', text: /Kuroko Workers/)
56
+
57
+ expect(page).to have_selector('#workers table tbody tr', count: 2)
58
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Suspend]', count: 1)
59
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Unsuspend]', count: 0)
60
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'WORKING', count: 1)
61
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'SUSPENDED', count: 0)
62
+
63
+ click_on 'Suspend'
64
+
65
+ expect(page).to have_selector('#workers table tbody tr', count: 2)
66
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Suspend]', count: 0)
67
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Unsuspend]', count: 1)
68
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'WORKING', count: 0)
69
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'SUSPENDED', count: 1)
70
+
71
+ click_on 'Unsuspend'
72
+
73
+ expect(page).to have_selector('#workers table tbody tr', count: 2)
74
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Suspend]', count: 1)
75
+ expect(page).to have_selector('#workers table tbody tr td .btn[value=Unsuspend]', count: 0)
76
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'WORKING', count: 1)
77
+ expect(page).to have_selector('#workers table tbody tr td .label', text: 'SUSPENDED', count: 0)
78
+ end
79
+ end
47
80
  end
@@ -0,0 +1,4 @@
1
+ require 'rails_helper'
2
+
3
+ describe Kuroko2::ExecutionHistory do
4
+ end
@@ -41,6 +41,7 @@ module Kuroko2::Workflow::Task
41
41
 
42
42
  expect(Kuroko2::Execution.all.size).to eq 0
43
43
  expect(Kuroko2::Log.all.count).to eq 1
44
+ expect(Kuroko2::ExecutionHistory.all.size).to eq 1
44
45
  end
45
46
  end
46
47
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kuroko2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naoto Takai
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-02-16 00:00:00.000000000 Z
12
+ date: 2018-03-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -566,6 +566,7 @@ files:
566
566
  - app/controllers/kuroko2/api/stats_controller.rb
567
567
  - app/controllers/kuroko2/application_controller.rb
568
568
  - app/controllers/kuroko2/dashboard_controller.rb
569
+ - app/controllers/kuroko2/execution_histories_controller.rb
569
570
  - app/controllers/kuroko2/execution_logs_controller.rb
570
571
  - app/controllers/kuroko2/executions_controller.rb
571
572
  - app/controllers/kuroko2/job_definition_stats_controller.rb
@@ -606,6 +607,7 @@ files:
606
607
  - app/models/kuroko2/api/job_instance_resource.rb
607
608
  - app/models/kuroko2/application_record.rb
608
609
  - app/models/kuroko2/execution.rb
610
+ - app/models/kuroko2/execution_history.rb
609
611
  - app/models/kuroko2/job_definition.rb
610
612
  - app/models/kuroko2/job_definition_tag.rb
611
613
  - app/models/kuroko2/job_instance.rb
@@ -625,6 +627,9 @@ files:
625
627
  - app/views/kaminari/list/_paginator.html.slim
626
628
  - app/views/kuroko2/dashboard/_taglist.html.slim
627
629
  - app/views/kuroko2/dashboard/index.html.slim
630
+ - app/views/kuroko2/execution_histories/dataset.json.jbuilder
631
+ - app/views/kuroko2/execution_histories/index.html.slim
632
+ - app/views/kuroko2/execution_histories/timeline.html.slim
628
633
  - app/views/kuroko2/execution_logs/index.json.jbuilder
629
634
  - app/views/kuroko2/executions/index.html.slim
630
635
  - app/views/kuroko2/job_definition_stats/execution_time.json.jbuilder
@@ -667,6 +672,7 @@ files:
667
672
  - app/views/layouts/kuroko2/application.html.slim
668
673
  - app/views/layouts/mailer.html.erb
669
674
  - app/views/layouts/mailer.text.erb
675
+ - bin/cleanup_old_execution_histories.rb
670
676
  - bin/cleanup_old_instances.rb
671
677
  - bin/remind_failure.rb
672
678
  - config/initializers/000_kuroko2.rb
@@ -697,6 +703,8 @@ files:
697
703
  - db/migrate/028_change_exit_status_to_unsigned_tinyint.rb
698
704
  - db/migrate/029_add_execution_id_to_process_signals.rb
699
705
  - db/migrate/030_add_notify_back_to_normal.rb
706
+ - db/migrate/031_add_suspended_to_workers.rb
707
+ - db/migrate/031_create_execution_histories.rb
700
708
  - lib/autoload/kuroko2/command/executor.rb
701
709
  - lib/autoload/kuroko2/command/kill.rb
702
710
  - lib/autoload/kuroko2/command/monitor.rb
@@ -756,6 +764,7 @@ files:
756
764
  - spec/command/monitor_spec.rb
757
765
  - spec/command/shell_spec.rb
758
766
  - spec/controllers/dashboard_controller_spec.rb
767
+ - spec/controllers/execution_histories_controller_spec.rb
759
768
  - spec/controllers/executions_controller_spec.rb
760
769
  - spec/controllers/job_definition_stats_controller_spec.rb
761
770
  - spec/controllers/job_definitions_controller_spec.rb
@@ -1737,6 +1746,7 @@ files:
1737
1746
  - spec/dummy/tmp/capybara/capybara-201707041237543496360175.png
1738
1747
  - spec/execution_logger/cloud_watch_logs_spec.rb
1739
1748
  - spec/factories/execution_factory.rb
1749
+ - spec/factories/execution_history_factory.rb
1740
1750
  - spec/factories/job_definition_factory.rb
1741
1751
  - spec/factories/job_instance_factory.rb
1742
1752
  - spec/factories/job_schedule_factory.rb
@@ -1749,6 +1759,7 @@ files:
1749
1759
  - spec/factories/user_factory.rb
1750
1760
  - spec/factories/worker_factory.rb
1751
1761
  - spec/features/dashborad_spec.rb
1762
+ - spec/features/execution_histories_spec.rb
1752
1763
  - spec/features/job_definition_spec.rb
1753
1764
  - spec/features/job_instance_spec.rb
1754
1765
  - spec/features/sign_in_and_out_spec.rb
@@ -1764,6 +1775,7 @@ files:
1764
1775
  - spec/mailers/notifications_spec.rb
1765
1776
  - spec/memory_sampler_spec.rb
1766
1777
  - spec/models/admin_assignment_spec.rb
1778
+ - spec/models/execution_history_spec.rb
1767
1779
  - spec/models/execution_spec.rb
1768
1780
  - spec/models/job_definition_spec.rb
1769
1781
  - spec/models/job_instance_spec.rb
@@ -1836,6 +1848,7 @@ test_files:
1836
1848
  - spec/command/monitor_spec.rb
1837
1849
  - spec/command/shell_spec.rb
1838
1850
  - spec/controllers/dashboard_controller_spec.rb
1851
+ - spec/controllers/execution_histories_controller_spec.rb
1839
1852
  - spec/controllers/executions_controller_spec.rb
1840
1853
  - spec/controllers/job_definition_stats_controller_spec.rb
1841
1854
  - spec/controllers/job_definitions_controller_spec.rb
@@ -2817,6 +2830,7 @@ test_files:
2817
2830
  - spec/dummy/tmp/capybara/capybara-201707041237543496360175.png
2818
2831
  - spec/execution_logger/cloud_watch_logs_spec.rb
2819
2832
  - spec/factories/execution_factory.rb
2833
+ - spec/factories/execution_history_factory.rb
2820
2834
  - spec/factories/job_definition_factory.rb
2821
2835
  - spec/factories/job_instance_factory.rb
2822
2836
  - spec/factories/job_schedule_factory.rb
@@ -2829,6 +2843,7 @@ test_files:
2829
2843
  - spec/factories/user_factory.rb
2830
2844
  - spec/factories/worker_factory.rb
2831
2845
  - spec/features/dashborad_spec.rb
2846
+ - spec/features/execution_histories_spec.rb
2832
2847
  - spec/features/job_definition_spec.rb
2833
2848
  - spec/features/job_instance_spec.rb
2834
2849
  - spec/features/sign_in_and_out_spec.rb
@@ -2844,6 +2859,7 @@ test_files:
2844
2859
  - spec/mailers/notifications_spec.rb
2845
2860
  - spec/memory_sampler_spec.rb
2846
2861
  - spec/models/admin_assignment_spec.rb
2862
+ - spec/models/execution_history_spec.rb
2847
2863
  - spec/models/execution_spec.rb
2848
2864
  - spec/models/job_definition_spec.rb
2849
2865
  - spec/models/job_instance_spec.rb