rrrspec-server 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.
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