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 +4 -4
- data/README.md +1 -0
- data/app/controllers/kuroko2/execution_histories_controller.rb +73 -0
- data/app/controllers/kuroko2/workers_controller.rb +17 -0
- data/app/models/kuroko2/execution_history.rb +8 -0
- data/app/views/kuroko2/execution_histories/dataset.json.jbuilder +10 -0
- data/app/views/kuroko2/execution_histories/index.html.slim +39 -0
- data/app/views/kuroko2/execution_histories/timeline.html.slim +33 -0
- data/app/views/kuroko2/workers/index.html.slim +26 -4
- data/bin/cleanup_old_execution_histories.rb +9 -0
- data/config/routes.rb +6 -1
- data/db/migrate/031_add_suspended_to_workers.rb +6 -0
- data/db/migrate/031_create_execution_histories.rb +19 -0
- data/lib/autoload/kuroko2/command/executor.rb +2 -1
- data/lib/autoload/kuroko2/command/shell.rb +2 -0
- data/lib/autoload/kuroko2/workflow/notifier/concerns/chat_message_builder.rb +3 -3
- data/lib/autoload/kuroko2/workflow/task/execute.rb +11 -0
- data/lib/kuroko2/version.rb +1 -1
- data/spec/command/shell_spec.rb +10 -0
- data/spec/controllers/execution_histories_controller_spec.rb +177 -0
- data/spec/controllers/workers_controller_spec.rb +14 -0
- data/spec/dummy/db/schema.rb +17 -1
- data/spec/factories/execution_history_factory.rb +15 -0
- data/spec/factories/worker_factory.rb +1 -0
- data/spec/features/execution_histories_spec.rb +36 -0
- data/spec/features/workers_spec.rb +33 -0
- data/spec/models/execution_history_spec.rb +4 -0
- data/spec/workflow/task/execute_spec.rb +1 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d66f084f8f4eabb9f6a0c0b26dad87b4009a4f97
|
4
|
+
data.tar.gz: 0256ee9b074d99821adbd5c1a8bf3bb280c29574
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4412638419a0984cde3aaf825f92b95a4e65eae33f660968635440decb04362b659b06a7a8aa0b18796a3f00dd551fa09a553d92fbe195edcda806f642d1fcf2
|
7
|
+
data.tar.gz: 0cb2e9266689d1636c67b5e7eae2ef56556d1b9188b0e2e56abd3c1fff739de7c041feb3ea98a8afe3382f1d0813c8da773773125aed2d244076a693be8d8884
|
data/README.md
CHANGED
@@ -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,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-
|
16
|
+
th.col-md-4 Execution
|
17
|
+
th.col-md-1
|
17
18
|
th.col-md-1
|
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.
|
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
|
'
|
@@ -39,3 +42,22 @@
|
|
39
42
|
role: 'button', class: 'btn btn-sm btn-default')
|
40
43
|
- else
|
41
44
|
'
|
45
|
+
td
|
46
|
+
- if !worker.suspendable
|
47
|
+
'
|
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?' })
|
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:
|
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).
|
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
|
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
|
-
"
|
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
|
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
|
data/lib/kuroko2/version.rb
CHANGED
data/spec/command/shell_spec.rb
CHANGED
@@ -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
|
data/spec/dummy/db/schema.rb
CHANGED
@@ -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:
|
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
|
@@ -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
|
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
|
+
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-
|
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
|