rrrspec-server 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffdf010821a66bdb732d362cfd9d3c33acf9e6ad
4
+ data.tar.gz: 06a8ab51406619a53ffa3131c8a356d630b6e2ec
5
+ SHA512:
6
+ metadata.gz: 3b86f71da951714df9558c4918e6bcf36b29815d97aeb907e71212762566f089baaf6e6fcf514df6e3569c8bdec17b11e50690c0df3d803eb4079805132c45d1
7
+ data.tar.gz: 3c0d6cd34faa4fc5b2853775c890053e27761f5d7e1f9eed510323b487e33206c82b983ce93b07d53e7ab8a1b5c4bf1b12565ea0f45adb29f51b151e72d8076d
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'rrrspec-client', path: '../'
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ load File.expand_path('../tasks/db.rake', __FILE__)
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rrrspec/server/cli'
4
+ RRRSpec::Server::CLI.start
@@ -0,0 +1,77 @@
1
+ class CreateTables < ActiveRecord::Migration
2
+ def change
3
+ create_table(:tasksets) do |t|
4
+ t.string :key
5
+ t.string :rsync_name
6
+ t.text :setup_command
7
+ t.text :slave_command
8
+ t.string :worker_type
9
+ t.integer :max_workers
10
+ t.integer :max_trials
11
+ t.string :taskset_class
12
+ t.integer :unknown_spec_timeout_sec
13
+ t.integer :least_timeout_sec
14
+ t.datetime :created_at
15
+
16
+ t.string :status
17
+ t.datetime :finished_at
18
+ t.text :log
19
+ end
20
+ add_index :tasksets, :key
21
+ add_index :tasksets, :rsync_name
22
+ add_index :tasksets, :taskset_class
23
+ add_index :tasksets, :created_at
24
+ add_index :tasksets, :status
25
+
26
+ create_table(:tasks) do |t|
27
+ t.string :key
28
+ t.references :taskset
29
+ t.string :status
30
+ t.integer :estimate_sec
31
+ t.string :spec_file
32
+ end
33
+ add_index :tasks, :key
34
+ add_index :tasks, :taskset_id
35
+ add_index :tasks, :status
36
+
37
+ create_table(:trials) do |t|
38
+ t.string :key
39
+ t.references :task
40
+ t.references :slave
41
+ t.datetime :started_at
42
+ t.datetime :finished_at
43
+ t.string :status
44
+ t.text :stdout
45
+ t.text :stderr
46
+ t.integer :passed
47
+ t.integer :pending
48
+ t.integer :failed
49
+ end
50
+ add_index :trials, :key
51
+ add_index :trials, :task_id
52
+ add_index :trials, :slave_id
53
+
54
+ create_table(:worker_logs) do |t|
55
+ t.string :key
56
+ t.string :worker_key
57
+ t.references :taskset
58
+ t.datetime :started_at
59
+ t.datetime :rsync_finished_at
60
+ t.datetime :setup_finished_at
61
+ t.datetime :finished_at
62
+ t.text :log
63
+ end
64
+ add_index :worker_logs, :key
65
+ add_index :worker_logs, :worker_key
66
+ add_index :worker_logs, :taskset_id
67
+
68
+ create_table(:slaves) do |t|
69
+ t.string :key
70
+ t.references :taskset
71
+ t.string :status
72
+ t.text :log
73
+ end
74
+ add_index :slaves, :key
75
+ add_index :slaves, :taskset_id
76
+ end
77
+ end
data/db/schema.rb ADDED
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20131105050718) do
15
+
16
+ create_table "slaves", :force => true do |t|
17
+ t.string "key"
18
+ t.integer "taskset_id"
19
+ t.string "status"
20
+ t.text "log"
21
+ end
22
+
23
+ add_index "slaves", ["key"], :name => "index_slaves_on_key"
24
+ add_index "slaves", ["taskset_id"], :name => "index_slaves_on_taskset_id"
25
+
26
+ create_table "tasks", :force => true do |t|
27
+ t.string "key"
28
+ t.integer "taskset_id"
29
+ t.string "status"
30
+ t.integer "estimate_sec"
31
+ t.string "spec_file"
32
+ end
33
+
34
+ add_index "tasks", ["key"], :name => "index_tasks_on_key"
35
+ add_index "tasks", ["status"], :name => "index_tasks_on_status"
36
+ add_index "tasks", ["taskset_id"], :name => "index_tasks_on_taskset_id"
37
+
38
+ create_table "tasksets", :force => true do |t|
39
+ t.string "key"
40
+ t.string "rsync_name"
41
+ t.text "setup_command"
42
+ t.text "slave_command"
43
+ t.string "worker_type"
44
+ t.integer "max_workers"
45
+ t.integer "max_trials"
46
+ t.string "taskset_class"
47
+ t.integer "unknown_spec_timeout_sec"
48
+ t.integer "least_timeout_sec"
49
+ t.datetime "created_at"
50
+ t.string "status"
51
+ t.datetime "finished_at"
52
+ t.text "log"
53
+ end
54
+
55
+ add_index "tasksets", ["created_at"], :name => "index_tasksets_on_created_at"
56
+ add_index "tasksets", ["key"], :name => "index_tasksets_on_key"
57
+ add_index "tasksets", ["rsync_name"], :name => "index_tasksets_on_rsync_name"
58
+ add_index "tasksets", ["status"], :name => "index_tasksets_on_status"
59
+ add_index "tasksets", ["taskset_class"], :name => "index_tasksets_on_taskset_class"
60
+
61
+ create_table "trials", :force => true do |t|
62
+ t.string "key"
63
+ t.integer "task_id"
64
+ t.integer "slave_id"
65
+ t.datetime "started_at"
66
+ t.datetime "finished_at"
67
+ t.string "status"
68
+ t.text "stdout"
69
+ t.text "stderr"
70
+ t.integer "passed"
71
+ t.integer "pending"
72
+ t.integer "failed"
73
+ end
74
+
75
+ add_index "trials", ["key"], :name => "index_trials_on_key"
76
+ add_index "trials", ["slave_id"], :name => "index_trials_on_slave_id"
77
+ add_index "trials", ["task_id"], :name => "index_trials_on_task_id"
78
+
79
+ create_table "worker_logs", :force => true do |t|
80
+ t.string "key"
81
+ t.string "worker_key"
82
+ t.integer "taskset_id"
83
+ t.datetime "started_at"
84
+ t.datetime "rsync_finished_at"
85
+ t.datetime "setup_finished_at"
86
+ t.datetime "finished_at"
87
+ t.text "log"
88
+ end
89
+
90
+ add_index "worker_logs", ["key"], :name => "index_worker_logs_on_key"
91
+ add_index "worker_logs", ["taskset_id"], :name => "index_worker_logs_on_taskset_id"
92
+ add_index "worker_logs", ["worker_key"], :name => "index_worker_logs_on_worker_key"
93
+
94
+ end
@@ -0,0 +1,186 @@
1
+ module RRRSpec
2
+ module Server
3
+ module Arbiter
4
+ module_function
5
+
6
+ def work_loop
7
+ loop { work }
8
+ end
9
+
10
+ def work
11
+ command, arg = ArbiterQueue.dequeue
12
+ case command
13
+ when 'cancel'
14
+ cancel(arg)
15
+ when 'check'
16
+ check(arg)
17
+ when 'fail'
18
+ fail(arg)
19
+ when 'trial'
20
+ trial(arg)
21
+ end
22
+ end
23
+
24
+ private
25
+ module_function
26
+
27
+ def cancel(taskset)
28
+ logger = TimedLogger.new(taskset)
29
+ logger.write("Cancel requested")
30
+
31
+ if [nil, 'running'].include?(taskset.status)
32
+ taskset.update_status('cancelled')
33
+ taskset.set_finished_time
34
+ taskset.clear_queue
35
+ end
36
+ PersisterQueue.enqueue(taskset)
37
+ ActiveTaskset.remove(taskset)
38
+ end
39
+
40
+ def check(taskset)
41
+ logger = TimedLogger.new(taskset)
42
+ logger.write("Check requested")
43
+
44
+ unless taskset.status == 'running'
45
+ PersisterQueue.enqueue(taskset)
46
+ ActiveTaskset.remove(taskset)
47
+ return
48
+ end
49
+
50
+ tasks_left = taskset.tasks_left
51
+ tasks_left.each do |task|
52
+ check_task(logger, taskset, task)
53
+ end
54
+
55
+ tasks_left = taskset.tasks_left
56
+ if tasks_left.empty?
57
+ if taskset.failed_count == 0
58
+ taskset.update_status('succeeded')
59
+ else
60
+ taskset.update_status('failed')
61
+ end
62
+ taskset.set_finished_time
63
+ PersisterQueue.enqueue(taskset)
64
+ ActiveTaskset.remove(taskset)
65
+ elsif taskset.queue_empty?
66
+ requeue_speculative(logger, taskset, tasks_left)
67
+ end
68
+ end
69
+
70
+ def check_task(logger, taskset, task)
71
+ running_trial = []
72
+ passed_trial = []
73
+ pending_trial = []
74
+ failed_trial = []
75
+ trials = task.trials
76
+ trials.each do |trial|
77
+ case trial.status
78
+ when nil, ''
79
+ if trial.slave.exist?
80
+ running_trial << trial
81
+ else
82
+ trial.finish('error', '', 'Failed by Arbiter', nil, nil, nil)
83
+ failed_trial << trial
84
+ end
85
+ when 'passed'
86
+ passed_trial << trial
87
+ when 'pending'
88
+ pending_trial << trial
89
+ when 'failed', 'error', 'timeout'
90
+ failed_trial << trial
91
+ end
92
+ end
93
+ case
94
+ when !passed_trial.empty?
95
+ task.update_status('passed')
96
+ taskset.incr_succeeded_count
97
+ taskset.finish_task(task)
98
+ when !pending_trial.empty?
99
+ task.update_status('pending')
100
+ taskset.incr_succeeded_count
101
+ taskset.finish_task(task)
102
+ when !running_trial.empty?
103
+ # Do nothing
104
+ when failed_trial.size >= taskset.max_trials
105
+ logger.write("Mark failed #{task.key}")
106
+ task.update_status('failed')
107
+ taskset.incr_failed_count
108
+ taskset.finish_task(task)
109
+ end
110
+ end
111
+
112
+ def requeue_speculative(logger, taskset, tasks_left)
113
+ running_tasks = Hash.new { |hash,key| hash[key] = [] }
114
+ not_running_tasks = []
115
+ tasks_left.each do |task|
116
+ trials = task.trials
117
+ if trials.any? { |trial| trial.status.blank? }
118
+ running_tasks[trials.size] << task
119
+ else
120
+ not_running_tasks << task
121
+ end
122
+ end
123
+
124
+ if not_running_tasks.empty?
125
+ task = running_tasks[running_tasks.keys.min].sample
126
+ logger.write("Speculatively enqueue the task #{task.key}")
127
+ taskset.enqueue_task(task)
128
+ else
129
+ task = not_running_tasks.sample
130
+ logger.write("Enqueue the task #{task.key}")
131
+ taskset.enqueue_task(task)
132
+ end
133
+ end
134
+
135
+ def fail(taskset)
136
+ logger = TimedLogger.new(taskset)
137
+ logger.write("Fail requested")
138
+
139
+ if [nil, 'running'].include?(taskset.status)
140
+ taskset.update_status('failed')
141
+ taskset.set_finished_time
142
+ taskset.clear_queue
143
+ end
144
+ PersisterQueue.enqueue(taskset)
145
+ ActiveTaskset.remove(taskset)
146
+ end
147
+
148
+ def trial(trial)
149
+ task = trial.task
150
+ return if task.status.present?
151
+ taskset = task.taskset
152
+ logger = TimedLogger.new(taskset)
153
+
154
+ if trial.status == nil
155
+ trial.finish('error', '', 'Failed by Arbiter', nil, nil, nil)
156
+ end
157
+
158
+ case trial.status
159
+ when 'passed'
160
+ task.update_status('passed')
161
+ taskset.incr_succeeded_count
162
+ taskset.finish_task(task)
163
+ when 'pending'
164
+ task.update_status('pending')
165
+ taskset.incr_succeeded_count
166
+ taskset.finish_task(task)
167
+ when 'failed', 'error', 'timeout'
168
+ trials = task.trials
169
+ finished_trials = []
170
+ trials.each do |trial|
171
+ finished_trials << trial unless [nil, ''].include?(trial.status)
172
+ end
173
+ if finished_trials.size >= taskset.max_trials
174
+ task.update_status('failed')
175
+ taskset.incr_failed_count
176
+ taskset.finish_task(task)
177
+ elsif trials.size < taskset.max_trials
178
+ task.update_status(nil)
179
+ logger.write("Enqueue the task #{task.key}")
180
+ taskset.reversed_enqueue_task(task)
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,107 @@
1
+ require 'rrrspec/server'
2
+ require 'thor'
3
+
4
+ module RRRSpec
5
+ module Server
6
+ class CLI < Thor
7
+ package_name 'RRRSpec'
8
+ default_command 'help'
9
+ class_option :config, aliases: '-c', type: :string, default: ''
10
+
11
+ no_commands do
12
+ def setup(conf)
13
+ RRRSpec.setup(conf, options[:config].split(':'))
14
+ end
15
+
16
+ def log_exception
17
+ yield
18
+ rescue
19
+ RRRSpec.logger.error($!)
20
+ raise
21
+ end
22
+
23
+ def auto_rebirth
24
+ Signal.trap('TERM') do
25
+ Signal.trap('TERM', 'DEFAULT')
26
+ Process.kill('TERM', 0)
27
+ end
28
+ loop do
29
+ pid = Process.fork do
30
+ log_exception { yield }
31
+ end
32
+ Process.waitpid(pid)
33
+ end
34
+ end
35
+
36
+ def daemonize
37
+ return unless options[:daemonize]
38
+ pidfile = options[:pidfile]
39
+
40
+ if pidfile
41
+ pidfile = File.absolute_path(pidfile)
42
+ if File.exists?(pidfile)
43
+ pid = open(pidfile, 'r') { |f| f.read.strip }
44
+ if pid
45
+ begin
46
+ if Process.kill(0, pid.to_i) == 1
47
+ $stderr.puts "Daemon process already exists: #{pid}"
48
+ exit 1
49
+ end
50
+ rescue Errno::ESRCH
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ Process.daemon
57
+
58
+ if pidfile
59
+ open(pidfile, 'w') { |f| f.write(Process.pid.to_s) }
60
+
61
+ parent_pid = Process.pid
62
+ at_exit do
63
+ if Process.pid == parent_pid
64
+ File.delete(pidfile)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ method_option :daemonize, type: :boolean
72
+ method_option :pidfile, type: :string
73
+ desc 'server', 'Run RRRSpec as a server'
74
+ def server
75
+ $0 = 'rrrspec server'
76
+ setup(ServerConfiguration.new)
77
+ daemonize
78
+ auto_rebirth do
79
+ ActiveRecord::Base.establish_connection(**RRRSpec.configuration.persistence_db)
80
+ Thread.abort_on_exception = true
81
+ Thread.fork { RRRSpec.pacemaker(RSyncInfo, 60, 5) }
82
+ Thread.fork { Dispatcher.work_loop }
83
+ Thread.fork { Arbiter.work_loop }
84
+ Thread.fork { Persister.work_loop }
85
+ Kernel.sleep
86
+ end
87
+ end
88
+
89
+ method_option :daemonize, type: :boolean
90
+ method_option :pidfile, type: :string
91
+ desc 'worker', 'Run RRRSpec as a worker'
92
+ def worker
93
+ $0 = 'rrrspec worker'
94
+ setup(WorkerConfiguration.new)
95
+ daemonize
96
+ auto_rebirth do
97
+ worker = Worker.create(RRRSpec.configuration.worker_type)
98
+ worker_runner = WorkerRunner.new(worker)
99
+ Thread.abort_on_exception = true
100
+ Thread.fork { RRRSpec.pacemaker(worker, 60, 5) }
101
+ Thread.fork { worker_runner.work_loop }
102
+ Kernel.sleep
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,54 @@
1
+ require 'facter'
2
+
3
+ module RRRSpec
4
+ module Server
5
+ class ServerConfiguration < Configuration
6
+ attr_accessor :rsync_server, :rsync_dir, :rsync_options
7
+ attr_accessor :persistence_db
8
+ attr_accessor :json_cache_path
9
+
10
+ def initialize
11
+ super()
12
+ @type = :server
13
+ end
14
+
15
+ def check_validity
16
+ validity = super
17
+
18
+ unless rsync_server and rsync_options and rsync_dir
19
+ $stderr.puts('The rsync options are not set')
20
+ validity = false
21
+ end
22
+
23
+ unless persistence_db
24
+ $stderr.puts('The database options are not set')
25
+ validity = false
26
+ end
27
+
28
+ validity
29
+ end
30
+ end
31
+
32
+ class WorkerConfiguration < Configuration
33
+ attr_accessor :working_dir, :worker_type, :slave_processes
34
+
35
+ def initialize
36
+ super()
37
+ @slave_processes = Facter.processorcount.to_i
38
+ @worker_type = 'default'
39
+ @type = :worker
40
+ end
41
+
42
+ def check_validity
43
+ validity = super
44
+
45
+ unless working_dir and worker_type
46
+ $stderr.puts('The worker options are not set')
47
+ validity = false
48
+ end
49
+
50
+ validity
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ module RRRSpec
2
+ module Server
3
+ class Dispatcher
4
+ def self.work
5
+ assigned = Hash.new { |hash,key| hash[key] = [] }
6
+ unemployed = Hash.new { |hash,key| hash[key] = [] }
7
+ Worker.list.each do |worker|
8
+ unless worker.exist?
9
+ worker.evict
10
+ else
11
+ taskset = worker.current_taskset
12
+ if taskset
13
+ assigned[taskset.key] << worker
14
+ else
15
+ unemployed[worker.worker_type] << worker
16
+ end
17
+ end
18
+ end
19
+
20
+ ActiveTaskset.list.each do |taskset|
21
+ should_mark_running = false
22
+ case taskset.status
23
+ when 'succeeded', 'cancelled', 'failed'
24
+ next
25
+ when nil
26
+ should_mark_running = true
27
+ end
28
+
29
+ # Cache the values
30
+ max_workers = taskset.max_workers
31
+ worker_type = taskset.worker_type
32
+ while max_workers > assigned[taskset.key].size
33
+ break if unemployed[worker_type].empty?
34
+ worker = unemployed[worker_type].pop
35
+ if should_mark_running
36
+ taskset.update_status('running')
37
+ should_mark_running = false
38
+ end
39
+ worker.enqueue_taskset(taskset)
40
+ assigned[taskset.key] << worker
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.work_loop
46
+ loop do
47
+ DispatcherQueue.wait
48
+ work
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end