mobilize-base 1.36 → 1.293
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +666 -1
- data/lib/mobilize-base.rb +1 -12
- data/lib/mobilize-base/extensions/array.rb +3 -8
- data/lib/mobilize-base/extensions/google_drive/acl.rb +1 -1
- data/lib/mobilize-base/extensions/google_drive/client_login_fetcher.rb +1 -2
- data/lib/mobilize-base/extensions/google_drive/file.rb +37 -11
- data/lib/mobilize-base/extensions/string.rb +6 -11
- data/lib/mobilize-base/extensions/yaml.rb +7 -10
- data/lib/mobilize-base/handlers/gbook.rb +38 -25
- data/lib/mobilize-base/handlers/gdrive.rb +4 -20
- data/lib/mobilize-base/handlers/gfile.rb +10 -64
- data/lib/mobilize-base/handlers/gridfs.rb +24 -19
- data/lib/mobilize-base/handlers/gsheet.rb +29 -45
- data/lib/mobilize-base/handlers/resque.rb +10 -17
- data/lib/mobilize-base/jobtracker.rb +196 -22
- data/lib/mobilize-base/models/job.rb +77 -107
- data/lib/mobilize-base/models/runner.rb +122 -36
- data/lib/mobilize-base/models/stage.rb +37 -18
- data/lib/mobilize-base/tasks.rb +13 -50
- data/lib/mobilize-base/version.rb +1 -1
- data/lib/samples/gdrive.yml +0 -15
- data/lib/samples/gridfs.yml +3 -0
- data/lib/samples/gsheet.yml +4 -4
- data/lib/samples/jobtracker.yml +6 -0
- data/mobilize-base.gemspec +3 -3
- data/test/base_job_rows.yml +11 -0
- data/test/mobilize-base_test.rb +106 -0
- data/test/test_base_1.yml +3 -0
- data/test/test_helper.rb +0 -155
- metadata +24 -36
- data/lib/mobilize-base/extensions/time.rb +0 -20
- data/lib/mobilize-base/helpers/job_helper.rb +0 -54
- data/lib/mobilize-base/helpers/jobtracker_helper.rb +0 -143
- data/lib/mobilize-base/helpers/runner_helper.rb +0 -83
- data/lib/mobilize-base/helpers/stage_helper.rb +0 -38
- data/lib/samples/gfile.yml +0 -9
- data/test/fixtures/base1_stage1.in.yml +0 -10
- data/test/fixtures/integration_expected.yml +0 -25
- data/test/fixtures/integration_jobs.yml +0 -12
- data/test/fixtures/is_due.yml +0 -97
- data/test/integration/mobilize-base_test.rb +0 -57
- data/test/unit/mobilize-base_test.rb +0 -33
@@ -1,38 +1,43 @@
|
|
1
|
-
require 'tempfile'
|
2
1
|
module Mobilize
|
3
2
|
module Gridfs
|
4
3
|
def Gridfs.config
|
5
4
|
Base.config('gridfs')
|
6
5
|
end
|
7
6
|
|
8
|
-
def Gridfs.
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
def Gridfs.grid
|
8
|
+
session = ::Mongoid.configure.sessions['default']
|
9
|
+
database_name = session['database']
|
10
|
+
host,port = session['hosts'].first.split(":")
|
11
|
+
return ::Mongo::GridFileSystem.new(::Mongo::Connection.new(host,port).db(database_name))
|
12
12
|
end
|
13
13
|
|
14
|
-
def Gridfs.
|
14
|
+
def Gridfs.read_by_dataset_path(dst_path,user_name,*args)
|
15
|
+
begin
|
16
|
+
zs=Gridfs.grid.open(dst_path,'r').read
|
17
|
+
return ::Zlib::Inflate.inflate(zs)
|
18
|
+
rescue
|
19
|
+
return nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def Gridfs.write_by_dataset_path(dst_path,string,user_name,*args)
|
15
24
|
zs = ::Zlib::Deflate.deflate(string)
|
16
25
|
raise "compressed string too large for Gridfs write" if zs.length > Gridfs.config['max_compressed_write_size']
|
17
|
-
|
18
|
-
|
19
|
-
curr_zs = curr_file.data if curr_file
|
20
|
-
#overwrite when there is a change
|
26
|
+
curr_zs = Gridfs.read_by_dataset_path(dst_path,user_name).to_s
|
27
|
+
#write a new version when there is a change
|
21
28
|
if curr_zs != zs
|
22
|
-
|
23
|
-
#create temp file w zstring
|
24
|
-
temp_file = ::Tempfile.new("#{string}#{Time.now.to_f}".to_md5)
|
25
|
-
temp_file.print(zs)
|
26
|
-
temp_file.close
|
27
|
-
#put data in file
|
28
|
-
Mongoid::GridFs.put(temp_file.path,:filename=>dst_path)
|
29
|
+
Gridfs.grid.open(dst_path,'w',:versions => Gridfs.config['max_versions']){|f| f.write(zs)}
|
29
30
|
end
|
30
31
|
return true
|
31
32
|
end
|
32
33
|
|
33
34
|
def Gridfs.delete(dst_path)
|
34
|
-
|
35
|
-
|
35
|
+
begin
|
36
|
+
Gridfs.grid.delete(dst_path)
|
37
|
+
return true
|
38
|
+
rescue
|
39
|
+
return nil
|
40
|
+
end
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
@@ -81,16 +81,15 @@ module Mobilize
|
|
81
81
|
|
82
82
|
def Gsheet.write_temp(target_path,gdrive_slot,tsv)
|
83
83
|
#find and delete temp sheet, if any
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
temp_sheet =
|
84
|
+
temp_path = [target_path.gridsafe,"temp"].join("/")
|
85
|
+
temp_sheet = Gsheet.find_by_path(temp_path,gdrive_slot)
|
86
|
+
temp_sheet.delete if temp_sheet
|
87
|
+
#write data to temp sheet
|
88
|
+
temp_sheet = Gsheet.find_or_create_by_path(temp_path,gdrive_slot)
|
89
89
|
#this step has a tendency to fail; if it does,
|
90
90
|
#don't fail the stage, mark it as false
|
91
91
|
begin
|
92
|
-
|
93
|
-
temp_sheet.write(tsv,gdrive_user)
|
92
|
+
temp_sheet.write(tsv,Gdrive.owner_name)
|
94
93
|
rescue
|
95
94
|
return nil
|
96
95
|
end
|
@@ -109,7 +108,7 @@ module Mobilize
|
|
109
108
|
#only give the user edit permissions if they're the ones
|
110
109
|
#creating it
|
111
110
|
target_sheet = Gsheet.find_or_create_by_path(target_path,gdrive_slot)
|
112
|
-
target_sheet.spreadsheet.update_acl(u.email) unless target_sheet.spreadsheet.acl_entry(u.email).ie{|e| e and e.role=="owner"}
|
111
|
+
target_sheet.spreadsheet.update_acl(u.email,"writer") unless target_sheet.spreadsheet.acl_entry(u.email).ie{|e| e and e.role=="owner"}
|
113
112
|
target_sheet.delete_sheet1
|
114
113
|
end
|
115
114
|
#pass it crop param to determine whether to shrink target sheet to fit data
|
@@ -127,43 +126,28 @@ module Mobilize
|
|
127
126
|
s = Stage.where(:path=>stage_path).first
|
128
127
|
u = s.job.runner.user
|
129
128
|
crop = s.params['crop'] || true
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
end
|
153
|
-
Gdrive.unslot_worker_by_path(stage_path)
|
154
|
-
stderr = nil
|
155
|
-
s.update_status(stdout)
|
156
|
-
signal = 0
|
157
|
-
rescue => exc
|
158
|
-
if retries < Gdrive.max_file_write_retries
|
159
|
-
retries +=1
|
160
|
-
sleep Gdrive.file_write_retry_delay
|
161
|
-
else
|
162
|
-
stdout = nil
|
163
|
-
stderr = [exc.to_s,"\n",exc.backtrace.join("\n")].join
|
164
|
-
signal = 500
|
165
|
-
end
|
166
|
-
end
|
129
|
+
begin
|
130
|
+
#get tsv to write from stage
|
131
|
+
source = s.sources(gdrive_slot).first
|
132
|
+
raise "Need source for gsheet write" unless source
|
133
|
+
tsv = source.read(u.name,gdrive_slot)
|
134
|
+
raise "No data source found for #{source.url}" unless tsv
|
135
|
+
stdout = if tsv.to_s.length == 0
|
136
|
+
#soft error; no data to write. Stage will complete.
|
137
|
+
"Write skipped for #{s.target.url}"
|
138
|
+
else
|
139
|
+
Dataset.write_by_url(s.target.url,tsv,u.name,gdrive_slot,crop)
|
140
|
+
#update status
|
141
|
+
"Write successful for #{s.target.url}"
|
142
|
+
end
|
143
|
+
Gdrive.unslot_worker_by_path(stage_path)
|
144
|
+
stderr = nil
|
145
|
+
s.update_status(stdout)
|
146
|
+
signal = 0
|
147
|
+
rescue => exc
|
148
|
+
stdout = nil
|
149
|
+
stderr = [exc.to_s,"\n",exc.backtrace.join("\n")].join
|
150
|
+
signal = 500
|
167
151
|
end
|
168
152
|
return {'out_str'=>stdout, 'err_str'=>stderr, 'signal' => signal}
|
169
153
|
end
|
@@ -19,17 +19,13 @@ module Mobilize
|
|
19
19
|
def Resque.workers(state="all")
|
20
20
|
workers = ::Resque.workers.select{|w| w.queues.first == Resque.queue_name}
|
21
21
|
return workers if state == 'all'
|
22
|
-
working_workers = workers.select{|w| w.job['queue']
|
22
|
+
working_workers = workers.select{|w| w.job['queue']== Resque.queue_name}
|
23
23
|
return working_workers if state == 'working'
|
24
24
|
idle_workers = workers.select{|w| w.job['queue'].nil?}
|
25
25
|
return idle_workers if state == 'idle'
|
26
26
|
stale_workers = workers.select{|w| Time.parse(w.started) < Jobtracker.deployed_at}
|
27
27
|
return stale_workers if state == 'stale'
|
28
|
-
timeout_workers = workers.select
|
29
|
-
w.job['payload'] and
|
30
|
-
w.job['payload']['class']!='Jobtracker' and
|
31
|
-
w.job['run_at'] < (Time.now.utc - Jobtracker.max_run_time)
|
32
|
-
end
|
28
|
+
timeout_workers = workers.select{|w| w.job['payload'] and w.job['payload']['class']!='Jobtracker' and w.job['runat'] < (Time.now.utc - Jobtracker.max_run_time)}
|
33
29
|
return timeout_workers if state == 'timeout'
|
34
30
|
raise "invalid state #{state}"
|
35
31
|
end
|
@@ -117,16 +113,10 @@ module Mobilize
|
|
117
113
|
stage_path = f['payload']['args'].first
|
118
114
|
email = begin
|
119
115
|
s = Stage.where(:path=>stage_path).first
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
else
|
125
|
-
s.job.runner.user.email
|
126
|
-
end
|
127
|
-
rescue ScriptError, StandardError
|
128
|
-
#jobs without stages are sent to admins
|
129
|
-
[Gdrive.admin_group_name,Gdrive.domain].join("@")
|
116
|
+
s.job.runner.user.email
|
117
|
+
rescue
|
118
|
+
#jobs without stages are sent to first admin
|
119
|
+
Jobtracker.admin_emails.first
|
130
120
|
end
|
131
121
|
exc_to_s = f['error']
|
132
122
|
if fjobs[email].nil?
|
@@ -148,7 +138,10 @@ module Mobilize
|
|
148
138
|
|
149
139
|
def Resque.start_workers(count=1)
|
150
140
|
count.times do
|
151
|
-
|
141
|
+
dir_envs = "MOBILIZE_ENV=#{Base.env} " +
|
142
|
+
"MOBILIZE_CONFIG_DIR=#{Base.config_dir} " +
|
143
|
+
"MOBILIZE_LOG_DIR=#{Base.log_dir}"
|
144
|
+
"(cd #{Base.root};rake #{dir_envs} mobilize_base:work) >> #{Resque.log_path} 2>&1 &".bash
|
152
145
|
end
|
153
146
|
end
|
154
147
|
|
@@ -1,13 +1,152 @@
|
|
1
1
|
module Mobilize
|
2
2
|
module Jobtracker
|
3
|
-
|
4
|
-
|
3
|
+
def Jobtracker.config
|
4
|
+
Base.config('jobtracker')
|
5
|
+
end
|
6
|
+
|
7
|
+
#modify this to increase the frequency of request cycles
|
8
|
+
def Jobtracker.cycle_freq
|
9
|
+
Jobtracker.config['cycle_freq']
|
10
|
+
end
|
11
|
+
|
12
|
+
#frequency of notifications
|
13
|
+
def Jobtracker.notification_freq
|
14
|
+
Jobtracker.config['notification_freq']
|
15
|
+
end
|
16
|
+
|
17
|
+
def Jobtracker.runner_read_freq
|
18
|
+
Jobtracker.config['runner_read_freq']
|
19
|
+
end
|
20
|
+
|
21
|
+
#long running tolerance
|
22
|
+
def Jobtracker.max_run_time
|
23
|
+
Jobtracker.config['max_run_time']
|
24
|
+
end
|
25
|
+
|
26
|
+
def Jobtracker.admins
|
27
|
+
Jobtracker.config['admins']
|
28
|
+
end
|
29
|
+
|
30
|
+
def Jobtracker.admin_emails
|
31
|
+
Jobtracker.admins.map{|a| a['email'] }
|
32
|
+
end
|
33
|
+
|
34
|
+
def Jobtracker.worker
|
35
|
+
Resque.find_worker_by_path("jobtracker")
|
36
|
+
end
|
37
|
+
|
38
|
+
def Jobtracker.workers(state="all")
|
39
|
+
Resque.workers(state)
|
40
|
+
end
|
41
|
+
|
42
|
+
def Jobtracker.status
|
43
|
+
args = Jobtracker.get_args
|
44
|
+
return args['status'] if args
|
45
|
+
job = Resque.jobs.select{|j| j['args'].first=='jobtracker'}.first
|
46
|
+
return 'queued' if job
|
47
|
+
return 'stopped'
|
48
|
+
end
|
49
|
+
|
50
|
+
def Jobtracker.update_status(msg)
|
51
|
+
#Jobtracker has no persistent database state
|
52
|
+
Resque.set_worker_args_by_path("jobtracker",{'status'=>msg})
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
def Jobtracker.restart
|
57
|
+
Jobtracker.stop!
|
58
|
+
Jobtracker.start
|
59
|
+
end
|
60
|
+
|
61
|
+
def Jobtracker.set_args(args)
|
62
|
+
Resque.set_worker_args(Jobtracker.worker,args)
|
63
|
+
return true
|
64
|
+
end
|
65
|
+
|
66
|
+
def Jobtracker.get_args
|
67
|
+
Resque.get_worker_args(Jobtracker.worker)
|
68
|
+
end
|
69
|
+
|
70
|
+
def Jobtracker.kill_workers
|
71
|
+
Resque.kill_workers
|
72
|
+
end
|
73
|
+
|
74
|
+
def Jobtracker.kill_idle_workers
|
75
|
+
Resque.kill_idle_workers
|
76
|
+
end
|
77
|
+
|
78
|
+
def Jobtracker.kill_idle_and_stale_workers
|
79
|
+
Resque.kill_idle_and_stale_workers
|
80
|
+
end
|
81
|
+
|
82
|
+
def Jobtracker.prep_workers
|
83
|
+
Resque.prep_workers
|
84
|
+
end
|
85
|
+
|
86
|
+
def Jobtracker.failures
|
87
|
+
Resque.failures
|
88
|
+
end
|
89
|
+
|
90
|
+
def Jobtracker.start
|
91
|
+
if Jobtracker.status!='stopped'
|
92
|
+
Jobtracker.update_status("Jobtracker still #{Jobtracker.status}")
|
93
|
+
else
|
94
|
+
#make sure that workers are running and at the right number
|
95
|
+
#Resque.prep_workers
|
96
|
+
#queue up the jobtracker (starts the perform method)
|
97
|
+
Jobtracker.enqueue!
|
98
|
+
end
|
99
|
+
return true
|
100
|
+
end
|
101
|
+
|
102
|
+
def Jobtracker.enqueue!
|
103
|
+
::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{})
|
104
|
+
end
|
105
|
+
|
106
|
+
def Jobtracker.restart!
|
107
|
+
Jobtracker.stop!
|
108
|
+
Jobtracker.start
|
109
|
+
return true
|
110
|
+
end
|
111
|
+
|
112
|
+
def Jobtracker.restart_workers!
|
113
|
+
Jobtracker.kill_workers
|
114
|
+
sleep 10
|
115
|
+
Jobtracker.prep_workers
|
116
|
+
Jobtracker.update_status("put workers back on the queue")
|
117
|
+
end
|
118
|
+
|
119
|
+
def Jobtracker.stop!
|
120
|
+
#send signal for Jobtracker to check for
|
121
|
+
Jobtracker.update_status('stopping')
|
122
|
+
sleep 5
|
123
|
+
i=0
|
124
|
+
while Jobtracker.status=='stopping'
|
125
|
+
Jobtracker.update_status("#{Jobtracker.to_s} still on queue, waiting")
|
126
|
+
sleep 5
|
127
|
+
i+=1
|
128
|
+
end
|
129
|
+
return true
|
130
|
+
end
|
131
|
+
|
132
|
+
def Jobtracker.last_notification
|
133
|
+
return Jobtracker.get_args["last_notification"] if Jobtracker.get_args
|
134
|
+
end
|
135
|
+
|
136
|
+
def Jobtracker.last_notification=(time)
|
137
|
+
Jobtracker.set_args({"last_notification"=>time})
|
138
|
+
end
|
139
|
+
|
140
|
+
def Jobtracker.notif_due?
|
141
|
+
last_duetime = Time.now.utc - Jobtracker.notification_freq
|
142
|
+
return (Jobtracker.last_notification.to_s.length==0 || Jobtracker.last_notification.to_datetime < last_duetime)
|
143
|
+
end
|
5
144
|
|
6
145
|
def Jobtracker.max_run_time_workers
|
7
146
|
#return workers who have been cranking away for 6+ hours
|
8
147
|
workers = Jobtracker.workers('working').select do |w|
|
9
|
-
w.job['
|
10
|
-
(Time.now.utc - Time.parse(w.job['
|
148
|
+
w.job['runat'].to_s.length>0 and
|
149
|
+
(Time.now.utc - Time.parse(w.job['runat'])) > Jobtracker.max_run_time
|
11
150
|
end
|
12
151
|
return workers
|
13
152
|
end
|
@@ -35,36 +174,24 @@ module Mobilize
|
|
35
174
|
end
|
36
175
|
end.flatten.join("\n\n")
|
37
176
|
u = User.where(:name=>email.split("@").first).first
|
38
|
-
|
39
|
-
|
40
|
-
n['body'] += "\n\n#{runner_dst.http_url}" if runner_dst and runner_dst.http_url
|
41
|
-
end
|
177
|
+
runner_dst = Dataset.find_by_url("gsheet://#{u.runner.path}")
|
178
|
+
n['body'] += "\n\n#{runner_dst.http_url}" if runner_dst and runner_dst.http_url
|
42
179
|
n['to'] = email
|
43
|
-
n['bcc'] =
|
180
|
+
n['bcc'] = Jobtracker.admin_emails.join(",")
|
44
181
|
notifs << n
|
45
182
|
end
|
46
183
|
end
|
47
184
|
lws = Jobtracker.max_run_time_workers
|
48
185
|
if lws.length>0
|
49
|
-
bod = begin
|
50
|
-
lws.map{|w| w.job['payload']['args']}.first.join("\n")
|
51
|
-
rescue
|
52
|
-
"Failed to get job names"
|
53
|
-
end
|
54
186
|
n = {}
|
55
187
|
n['subject'] = "#{lws.length.to_s} max run time jobs"
|
56
|
-
n['body'] =
|
57
|
-
n['to'] =
|
188
|
+
n['body'] = lws.map{|w| %{spec:#{w['spec']} stg:#{w['stg']} runat:#{w['runat'].to_s}}}.join("\n\n")
|
189
|
+
n['to'] = Jobtracker.admin_emails.join(",")
|
58
190
|
notifs << n
|
59
191
|
end
|
60
192
|
#deliver each email generated
|
61
193
|
notifs.each do |notif|
|
62
|
-
|
63
|
-
Email.write(notif).deliver
|
64
|
-
rescue
|
65
|
-
#log email on failure
|
66
|
-
Jobtracker.update_status("Failed to deliver #{notif.to_s}")
|
67
|
-
end
|
194
|
+
Email.write(notif).deliver
|
68
195
|
end
|
69
196
|
#update notification time so JT knows to wait a while
|
70
197
|
Jobtracker.last_notification = Time.now.utc.to_s
|
@@ -116,5 +243,52 @@ module Mobilize
|
|
116
243
|
end.to_s.strip
|
117
244
|
Time.parse(deploy_time)
|
118
245
|
end
|
246
|
+
|
247
|
+
#test methods
|
248
|
+
def Jobtracker.restart_test_redis
|
249
|
+
Jobtracker.stop_test_redis
|
250
|
+
if !system("which redis-server")
|
251
|
+
raise "** can't find `redis-server` in your path, you need redis to run Resque and Mobilize"
|
252
|
+
end
|
253
|
+
"redis-server #{Base.root}/test/redis-test.conf".bash
|
254
|
+
end
|
255
|
+
|
256
|
+
def Jobtracker.stop_test_redis
|
257
|
+
processes = `ps -A -o pid,command | grep [r]edis-test`.split($/)
|
258
|
+
pids = processes.map { |process| process.split(" ")[0] }
|
259
|
+
puts "Killing test redis server..."
|
260
|
+
pids.each { |pid| Process.kill("TERM", pid.to_i) }
|
261
|
+
puts "removing redis db dump file"
|
262
|
+
sleep 5
|
263
|
+
`rm -f #{Base.root}/test/dump.rdb #{Base.root}/test/dump-cluster.rdb`
|
264
|
+
end
|
265
|
+
|
266
|
+
def Jobtracker.set_test_env
|
267
|
+
ENV['MOBILIZE_ENV']='test'
|
268
|
+
::Resque.redis="localhost:9736"
|
269
|
+
mongoid_config_path = "#{Base.root}/config/mobilize/mongoid.yml"
|
270
|
+
Mongoid.load!(mongoid_config_path, Base.env)
|
271
|
+
end
|
272
|
+
|
273
|
+
def Jobtracker.drop_test_db
|
274
|
+
Jobtracker.set_test_env
|
275
|
+
Mongoid.session(:default).collections.each do |collection|
|
276
|
+
unless collection.name =~ /^system\./
|
277
|
+
collection.drop
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def Jobtracker.build_test_runner(user_name)
|
283
|
+
Jobtracker.set_test_env
|
284
|
+
u = User.where(:name=>user_name).first
|
285
|
+
Jobtracker.update_status("delete old books and datasets")
|
286
|
+
# delete any old runner from previous test runs
|
287
|
+
gdrive_slot = Gdrive.owner_email
|
288
|
+
u.runner.gsheet(gdrive_slot).spreadsheet.delete
|
289
|
+
Jobtracker.update_status("enqueue jobtracker, wait 45s")
|
290
|
+
Mobilize::Jobtracker.start
|
291
|
+
sleep 45
|
292
|
+
end
|
119
293
|
end
|
120
294
|
end
|