mobilize-base 1.0.2 → 1.0.4
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.
- data/.gitignore +5 -0
- data/LICENSE.txt +202 -20
- data/README.md +219 -138
- data/Rakefile +1 -2
- data/lib/mobilize-base/extensions/google_drive/acl.rb +25 -0
- data/lib/mobilize-base/extensions/google_drive/client_login_fetcher.rb +49 -0
- data/lib/mobilize-base/extensions/google_drive/file.rb +80 -0
- data/lib/mobilize-base/extensions/{google_drive.rb → google_drive/worksheet.rb} +46 -173
- data/lib/mobilize-base/extensions/resque.rb +18 -24
- data/lib/mobilize-base/extensions/string.rb +12 -0
- data/lib/mobilize-base/handlers/gbook.rb +14 -47
- data/lib/mobilize-base/handlers/gdrive.rb +17 -18
- data/lib/mobilize-base/handlers/gfile.rb +18 -39
- data/lib/mobilize-base/handlers/gridfs.rb +43 -0
- data/lib/mobilize-base/handlers/gsheet.rb +48 -99
- data/lib/mobilize-base/jobtracker.rb +29 -15
- data/lib/mobilize-base/models/dataset.rb +33 -35
- data/lib/mobilize-base/models/job.rb +21 -168
- data/lib/mobilize-base/models/runner.rb +178 -0
- data/lib/mobilize-base/models/task.rb +137 -0
- data/lib/mobilize-base/models/user.rb +47 -0
- data/lib/mobilize-base/rakes.rb +59 -0
- data/lib/mobilize-base/version.rb +1 -1
- data/lib/mobilize-base.rb +20 -9
- data/lib/samples/gdrive.yml +12 -12
- data/lib/samples/gridfs.yml +9 -0
- data/lib/samples/gsheet.yml +6 -0
- data/lib/samples/jobtracker.yml +9 -9
- data/lib/samples/mongoid.yml +3 -3
- data/mobilize-base.gemspec +1 -1
- data/test/base1_task1.yml +3 -0
- data/test/base_job_rows.yml +13 -0
- data/test/mobilize-base_test.rb +59 -0
- metadata +20 -9
- data/lib/mobilize-base/handlers/mongodb.rb +0 -32
- data/lib/mobilize-base/models/requestor.rb +0 -232
- data/lib/mobilize-base/tasks.rb +0 -43
- data/test/mobilize_test.rb +0 -108
@@ -1,7 +1,7 @@
|
|
1
1
|
module Mobilize
|
2
2
|
module Jobtracker
|
3
3
|
def Jobtracker.config
|
4
|
-
Base.config('jobtracker')
|
4
|
+
Base.config('jobtracker')
|
5
5
|
end
|
6
6
|
|
7
7
|
#modify this to increase the frequency of request cycles
|
@@ -14,8 +14,8 @@ module Mobilize
|
|
14
14
|
Jobtracker.config['notification_freq']
|
15
15
|
end
|
16
16
|
|
17
|
-
def Jobtracker.
|
18
|
-
Jobtracker.config['
|
17
|
+
def Jobtracker.runner_read_freq
|
18
|
+
Jobtracker.config['runner_read_freq']
|
19
19
|
end
|
20
20
|
|
21
21
|
#long running tolerance
|
@@ -28,11 +28,11 @@ module Mobilize
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def Jobtracker.admin_emails
|
31
|
-
Jobtracker.admins.map{|a| a['email']}
|
31
|
+
Jobtracker.admins.map{|a| a['email'] }
|
32
32
|
end
|
33
33
|
|
34
34
|
def Jobtracker.worker
|
35
|
-
Resque.
|
35
|
+
Resque.find_worker_by_path("jobtracker")
|
36
36
|
end
|
37
37
|
|
38
38
|
def Jobtracker.workers(state="all")
|
@@ -49,7 +49,7 @@ module Mobilize
|
|
49
49
|
|
50
50
|
def Jobtracker.update_status(msg)
|
51
51
|
#Jobtracker has no persistent database state
|
52
|
-
Resque.
|
52
|
+
Resque.set_worker_args_by_path("jobtracker",{'status'=>msg})
|
53
53
|
return true
|
54
54
|
end
|
55
55
|
|
@@ -96,7 +96,7 @@ module Mobilize
|
|
96
96
|
end
|
97
97
|
|
98
98
|
def Jobtracker.enqueue!
|
99
|
-
::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{
|
99
|
+
::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{})
|
100
100
|
end
|
101
101
|
|
102
102
|
def Jobtracker.restart!
|
@@ -107,8 +107,9 @@ module Mobilize
|
|
107
107
|
|
108
108
|
def Jobtracker.restart_workers!
|
109
109
|
Jobtracker.kill_workers
|
110
|
-
sleep
|
110
|
+
sleep 10
|
111
111
|
Jobtracker.prep_workers
|
112
|
+
Jobtracker.update_status("put workers back on the queue")
|
112
113
|
end
|
113
114
|
|
114
115
|
def Jobtracker.stop!
|
@@ -117,7 +118,7 @@ module Mobilize
|
|
117
118
|
sleep 5
|
118
119
|
i=0
|
119
120
|
while Jobtracker.status=='stopping'
|
120
|
-
|
121
|
+
Jobtracker.update_status("#{Jobtracker.to_s} still on queue, waiting")
|
121
122
|
sleep 5
|
122
123
|
i+=1
|
123
124
|
end
|
@@ -175,7 +176,7 @@ module Mobilize
|
|
175
176
|
notifs.each do |notif|
|
176
177
|
Email.write(n['subj'],notif['body']).deliver
|
177
178
|
Jobtracker.last_notification=Time.now.utc.to_s
|
178
|
-
"Sent notification at #{Jobtracker.last_notification}"
|
179
|
+
Jobtracker.update_status("Sent notification at #{Jobtracker.last_notification}")
|
179
180
|
end
|
180
181
|
end
|
181
182
|
return true
|
@@ -183,13 +184,14 @@ module Mobilize
|
|
183
184
|
|
184
185
|
def Jobtracker.perform(id,*args)
|
185
186
|
while Jobtracker.status != 'stopping'
|
186
|
-
|
187
|
+
users = User.all
|
187
188
|
Jobtracker.run_notifications
|
188
|
-
|
189
|
-
|
189
|
+
users.each do |u|
|
190
|
+
r = u.runner
|
191
|
+
Jobtracker.update_status("Checking #{r.path}")
|
190
192
|
if r.is_due?
|
191
193
|
r.enqueue!
|
192
|
-
Jobtracker.update_status("Enqueued
|
194
|
+
Jobtracker.update_status("Enqueued #{r.path}")
|
193
195
|
end
|
194
196
|
end
|
195
197
|
sleep 5
|
@@ -221,7 +223,7 @@ module Mobilize
|
|
221
223
|
def Jobtracker.set_test_env
|
222
224
|
ENV['MOBILIZE_ENV']='test'
|
223
225
|
::Resque.redis="localhost:9736"
|
224
|
-
mongoid_config_path = "#{Base.root}/config/mongoid.yml"
|
226
|
+
mongoid_config_path = "#{Base.root}/config/mobilize/mongoid.yml"
|
225
227
|
Mongoid.load!(mongoid_config_path, Base.env)
|
226
228
|
end
|
227
229
|
|
@@ -233,5 +235,17 @@ module Mobilize
|
|
233
235
|
end
|
234
236
|
end
|
235
237
|
end
|
238
|
+
|
239
|
+
def Jobtracker.build_test_runner(user_name)
|
240
|
+
Jobtracker.set_test_env
|
241
|
+
u = User.where(:name=>user_name).first
|
242
|
+
Jobtracker.update_status("delete old books and datasets")
|
243
|
+
# delete any old runner from previous test runs
|
244
|
+
gdrive_slot = Gdrive.owner_email
|
245
|
+
u.runner.gsheet(gdrive_slot).spreadsheet.delete
|
246
|
+
Jobtracker.update_status("enqueue jobtracker, wait 45s")
|
247
|
+
Mobilize::Jobtracker.start
|
248
|
+
sleep 45
|
249
|
+
end
|
236
250
|
end
|
237
251
|
end
|
@@ -2,69 +2,67 @@ module Mobilize
|
|
2
2
|
class Dataset
|
3
3
|
include Mongoid::Document
|
4
4
|
include Mongoid::Timestamps
|
5
|
-
field :requestor_id, type: String
|
6
5
|
field :handler, type: String
|
7
|
-
field :
|
6
|
+
field :path, type: String
|
8
7
|
field :url, type: String
|
9
|
-
field :
|
8
|
+
field :raw_size, type: Fixnum
|
10
9
|
field :last_cached_at, type: Time
|
10
|
+
field :last_cache_handler, type: String
|
11
11
|
field :last_read_at, type: Time
|
12
12
|
field :cache_expire_at, type: Time
|
13
13
|
|
14
|
-
index({
|
15
|
-
index({ handler: 1})
|
16
|
-
index({ name: 1})
|
17
|
-
|
18
|
-
before_destroy :destroy_cache
|
14
|
+
index({ handler: 1, path: 1}, { unique: true})
|
19
15
|
|
20
16
|
def read
|
21
17
|
dst = self
|
22
|
-
|
23
|
-
return dst.read_cache
|
24
|
-
else
|
25
|
-
return "Mobilize::#{dst.handler.humanize}".constantize.read_by_dst_id(dst.id.to_s)
|
26
|
-
end
|
18
|
+
return "Mobilize::#{dst.handler.humanize}".constantize.read_by_path(dst.path)
|
27
19
|
end
|
28
20
|
|
29
|
-
def Dataset.
|
30
|
-
Dataset.where(handler: handler,
|
31
|
-
end
|
32
|
-
|
33
|
-
def Dataset.find_or_create_by_handler_and_name(handler,name)
|
34
|
-
dst = Dataset.where(handler: handler, name: name).first
|
35
|
-
dst = Dataset.create(handler: handler, name: name) unless dst
|
36
|
-
return dst
|
21
|
+
def Dataset.find_by_handler_and_path(handler,path)
|
22
|
+
Dataset.where(handler: handler, path: path).first
|
37
23
|
end
|
38
24
|
|
39
|
-
def Dataset.
|
40
|
-
dst = Dataset.where(
|
41
|
-
dst = Dataset.create(
|
25
|
+
def Dataset.find_or_create_by_handler_and_path(handler,path)
|
26
|
+
dst = Dataset.where(handler: handler, path: path).first
|
27
|
+
dst = Dataset.create(handler: handler, path: path) unless dst
|
42
28
|
return dst
|
43
29
|
end
|
44
30
|
|
45
|
-
def write(
|
31
|
+
def write(string)
|
46
32
|
dst = self
|
47
|
-
dst.handler.humanize.constantize.
|
33
|
+
"Mobilize::#{dst.handler.humanize}".constantize.write_by_path(dst.path,string)
|
34
|
+
dst.raw_size = string.length
|
48
35
|
dst.save!
|
49
36
|
return true
|
50
37
|
end
|
51
38
|
|
52
|
-
def
|
39
|
+
def cache_valid?
|
53
40
|
dst = self
|
54
|
-
dst.
|
55
|
-
return Mongodb.read_by_filename(dst.id.to_s)
|
41
|
+
return true if dst.last_cached_at and (dst.cache_expire_at.nil? or dst.cache_expire_at > Time.now.utc)
|
56
42
|
end
|
57
43
|
|
58
|
-
def
|
44
|
+
def read_cache(cache_handler="gridfs")
|
59
45
|
dst = self
|
60
|
-
|
61
|
-
|
62
|
-
|
46
|
+
if cache_valid?
|
47
|
+
dst.update_attributes(:last_read_at=>Time.now.utc)
|
48
|
+
return "Mobilize::#{cache_handler.humanize}".constantize.read([dst.handler,dst.path].join("://"))
|
49
|
+
else
|
50
|
+
raise "Cache invalid or not found for #{cache_handler}://#{dst.path}"
|
51
|
+
end
|
63
52
|
end
|
64
53
|
|
65
|
-
def
|
66
|
-
|
54
|
+
def write_cache(string,expire_at=nil,cache_handler="gridfs")
|
55
|
+
dst = self
|
56
|
+
"Mobilize::#{cache_handler.humanize}".constantize.write([dst.handler,dst.path].join("://"),string)
|
57
|
+
dst.update_attributes(:last_cached_at=>Time.now.utc,
|
58
|
+
:last_cache_handler=>cache_handler.to_s.downcase,
|
59
|
+
:cache_expire_at=>expire_at,
|
60
|
+
:size=>string.length)
|
61
|
+
return true
|
67
62
|
end
|
68
63
|
|
64
|
+
def delete_cache(cache_handler="gridfs")
|
65
|
+
return "Mobilize::#{cache_handler.humanize}".constantize.delete(dst.handler, dst.path)
|
66
|
+
end
|
69
67
|
end
|
70
68
|
end
|
@@ -2,199 +2,52 @@ module Mobilize
|
|
2
2
|
class Job
|
3
3
|
include Mongoid::Document
|
4
4
|
include Mongoid::Timestamps
|
5
|
-
field :
|
6
|
-
field :
|
7
|
-
field :
|
8
|
-
field :schedule, type: String
|
9
|
-
field :active_task, type: String
|
10
|
-
field :tasks, type: String
|
5
|
+
field :path, type: String
|
6
|
+
field :active, type: Boolean
|
7
|
+
field :trigger, type: String
|
11
8
|
field :status, type: String
|
12
|
-
field :last_error, type: String
|
13
|
-
field :last_trace, type: String
|
14
9
|
field :last_completed_at, type: Time
|
15
|
-
field :datasets, type: String #name of data sources
|
16
|
-
field :params, type: String #JSON
|
17
|
-
field :destination, type: String #output destination - could be file, could be sheet
|
18
10
|
|
19
|
-
index({
|
20
|
-
index({ name: 1})
|
11
|
+
index({ path: 1})
|
21
12
|
|
22
|
-
|
23
|
-
|
24
|
-
def worker
|
25
|
-
j = self
|
26
|
-
Mobilize::Resque.find_worker_by_mongo_id(j.id.to_s)
|
27
|
-
end
|
28
|
-
|
29
|
-
def dataset_array
|
13
|
+
def name
|
30
14
|
j = self
|
31
|
-
|
32
|
-
dsts = j.datasets.split(",").map{|dst| dst.strip}
|
33
|
-
dsts.map do |ps|
|
34
|
-
#prepend jobspec title if there is no path separator
|
35
|
-
full_ps = ps.index("/") ? ps : [r.jobspec_title,ps].join("/")
|
36
|
-
#find or create dataset for this sheet
|
37
|
-
dst = Dataset.find_or_create_by_handler_and_name("gsheet",full_ps)
|
38
|
-
dst.update_attributes(:requestor_id=>r.id.to_s) unless dst.requestor_id
|
39
|
-
dst
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def task_array
|
44
|
-
self.tasks.split(",").map{|t| t.strip}
|
45
|
-
end
|
46
|
-
|
47
|
-
def task_output_dsts
|
48
|
-
j = self
|
49
|
-
r = j.requestor
|
50
|
-
dst_names = j.task_array.map{|tname| [r.name,j.name,tname].join("/")}
|
51
|
-
dst_names.map do |dst_name|
|
52
|
-
Dataset.find_or_create_by_requestor_id_and_handler_and_name(r.id.to_s,'mongodb',dst_name)
|
53
|
-
end
|
15
|
+
j.path.split("/").last
|
54
16
|
end
|
55
17
|
|
56
|
-
def
|
18
|
+
def tasks
|
57
19
|
j = self
|
58
|
-
|
20
|
+
Task.where(:path=>/^#{j.path.escape_regex}/).to_a.sort_by{|t| t.path}
|
59
21
|
end
|
60
22
|
|
61
|
-
def Job.
|
62
|
-
Job.where(:
|
63
|
-
|
64
|
-
|
65
|
-
def Job.find_all_by_requestor_id(requestor_id)
|
66
|
-
Job.where(:requestor_id=>requestor_id).to_a
|
67
|
-
end
|
68
|
-
|
69
|
-
def Job.find_or_create_by_requestor_id_and_name(requestor_id,name)
|
70
|
-
j = Job.where(:requestor_id=>requestor_id, :name=>name).first
|
71
|
-
j = Job.create(:requestor_id=>requestor_id, :name=>name) unless j
|
23
|
+
def Job.find_or_create_by_path(path)
|
24
|
+
j = Job.where(:path=>path).first
|
25
|
+
j = Job.create(:path=>path) unless j
|
72
26
|
return j
|
73
27
|
end
|
74
28
|
|
75
|
-
#called by Resque
|
76
|
-
def Job.perform(id,*args)
|
77
|
-
j = Job.find(id)
|
78
|
-
r = j.requestor
|
79
|
-
handler,method_name = j.active_task.split(".")
|
80
|
-
begin
|
81
|
-
j.update_status(%{Starting #{j.active_task} task at #{Time.now.utc}})
|
82
|
-
task_output = "Mobilize::#{handler.humanize}".constantize.send("#{method_name}_by_job_id",id)
|
83
|
-
#this allows user to return false if the stage didn't go as expected and needs to retry
|
84
|
-
#e.g. tried to write to Google but all the accounts were in use
|
85
|
-
return false if task_output == false
|
86
|
-
task_output_dst = j.task_output_dsts[j.task_idx]
|
87
|
-
task_output = task_output.to_s unless task_output.class == String
|
88
|
-
task_output_dst.write_cache(task_output)
|
89
|
-
if j.active_task == j.task_array.last
|
90
|
-
j.active_task = nil
|
91
|
-
j.last_error = ""
|
92
|
-
j.last_trace = ""
|
93
|
-
j.last_completed_at = Time.now.utc
|
94
|
-
j.status = %{Completed all tasks at #{Time.now.utc}}
|
95
|
-
j.save!
|
96
|
-
#check for any dependent jobs, if there are, enqueue them
|
97
|
-
r = j.requestor
|
98
|
-
dep_jobs = Job.where(:active=>true, :requestor_id=>r.id.to_s, :schedule=>"after #{j.name}").to_a
|
99
|
-
dep_jobs += Job.where(:active=>true, :schedule=>"after #{r.name}/#{j.name}").to_a
|
100
|
-
#put begin/rescue so all dependencies run
|
101
|
-
dep_jobs.each{|dj| begin;dj.enqueue! unless dj.is_working?;rescue;end}
|
102
|
-
else
|
103
|
-
#set next task
|
104
|
-
j.active_task = j.task_array[j.task_idx+1]
|
105
|
-
j.save!
|
106
|
-
#queue up next task
|
107
|
-
j.enqueue!
|
108
|
-
end
|
109
|
-
rescue ScriptError,StandardError => exc
|
110
|
-
#record the failure in Job so it appears on spec sheets
|
111
|
-
j.status='failed'
|
112
|
-
j.save!
|
113
|
-
j.update_status("Failed at #{Time.now.utc.to_s}")
|
114
|
-
j.update_attributes(:last_error=>exc.to_s,:last_trace=>exc.backtrace.to_s)
|
115
|
-
[exc.to_s,exc.backtrace.to_s].join("=>").oputs
|
116
|
-
#raising here will cause the failure to show on the Resque UI
|
117
|
-
raise exc
|
118
|
-
end
|
119
|
-
return true
|
120
|
-
end
|
121
|
-
|
122
|
-
def enqueue!
|
123
|
-
j = self
|
124
|
-
r = j.requestor
|
125
|
-
j.update_attributes(:active_task=>j.task_array.first) if j.active_task.blank?
|
126
|
-
::Resque::Job.create("mobilize",Job,j.id.to_s,%{#{r.name}=>#{j.name}})
|
127
|
-
return true
|
128
|
-
end
|
129
|
-
|
130
29
|
#convenience methods
|
131
|
-
def
|
132
|
-
j = self
|
133
|
-
return Requestor.find(j.requestor_id)
|
134
|
-
end
|
135
|
-
|
136
|
-
def restart
|
137
|
-
j = self
|
138
|
-
j.update_attributes(:last_completed_at=>nil)
|
139
|
-
return true
|
140
|
-
end
|
141
|
-
|
142
|
-
def prior_task
|
143
|
-
j = self
|
144
|
-
return nil if j.active_task.nil?
|
145
|
-
task_idx = j.task_array.index(j.active_task)
|
146
|
-
return nil if task_idx==0
|
147
|
-
return j.task_array[task_idx-1]
|
148
|
-
end
|
149
|
-
|
150
|
-
def destination_url
|
151
|
-
j = self
|
152
|
-
return nil if j.destination.nil?
|
153
|
-
destination = j.destination
|
154
|
-
dst = if j.task_array.last == 'gsheet.write'
|
155
|
-
destination = [j.requestor.jobspec_title,j.destination].join("/") if destination.split("/").length==1
|
156
|
-
Dataset.find_by_handler_and_name('gsheet',destination)
|
157
|
-
elsif j.task_array.last == 'gfile.write'
|
158
|
-
#all gfiles must end in gz
|
159
|
-
destination += ".gz" unless destination.ends_with?(".gz")
|
160
|
-
destination = [s.requestor.name,"_"].join + destination unless destination.starts_with?([s.requestor.name,"_"].join)
|
161
|
-
Dataset.find_by_handler_and_name('gfile',destination)
|
162
|
-
end
|
163
|
-
return dst.url if dst
|
164
|
-
end
|
165
|
-
|
166
|
-
def worker_args
|
167
|
-
j = self
|
168
|
-
Jobtracker.get_worker_args(j.worker)
|
169
|
-
end
|
170
|
-
|
171
|
-
def set_worker_args(args)
|
172
|
-
j = self
|
173
|
-
Jobtracker.set_worker_args(j.worker,args)
|
174
|
-
end
|
175
|
-
|
176
|
-
def update_status(msg)
|
30
|
+
def runner
|
177
31
|
j = self
|
178
|
-
j.
|
179
|
-
|
180
|
-
return true
|
32
|
+
runner_path = j.path.split("/")[0..-2].join("/")
|
33
|
+
return Runner.where(:path=>runner_path).first
|
181
34
|
end
|
182
35
|
|
183
36
|
def is_working?
|
184
37
|
j = self
|
185
|
-
|
38
|
+
j.tasks.select{|t| t.is_working?}.compact.length>0
|
186
39
|
end
|
187
40
|
|
188
41
|
def is_due?
|
189
42
|
j = self
|
190
|
-
return false if j.is_working? or j.active == false or j.
|
43
|
+
return false if j.is_working? or j.active == false or j.trigger.to_s.starts_with?("after")
|
191
44
|
last_run = j.last_completed_at
|
192
|
-
#check
|
193
|
-
|
194
|
-
return true if
|
45
|
+
#check trigger
|
46
|
+
trigger = j.trigger
|
47
|
+
return true if trigger == 'once'
|
195
48
|
#strip the "every" from the front if present
|
196
|
-
|
197
|
-
value,unit,operator,job_utctime =
|
49
|
+
trigger = trigger.gsub("every","").gsub("."," ").strip
|
50
|
+
value,unit,operator,job_utctime = trigger.split(" ")
|
198
51
|
curr_utctime = Time.now.utc
|
199
52
|
curr_utcdate = curr_utctime.to_date.strftime("%Y-%m-%d")
|
200
53
|
if job_utctime
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module Mobilize
|
2
|
+
class Runner
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
field :path, type: String
|
6
|
+
field :active, type: Boolean
|
7
|
+
field :status, type: String
|
8
|
+
field :last_run, type: Time
|
9
|
+
|
10
|
+
index({ path: 1})
|
11
|
+
|
12
|
+
def headers
|
13
|
+
%w{name active trigger status task1 task2 task3 task4 task5}
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_cached_at
|
17
|
+
r = self
|
18
|
+
Dataset.find_or_create_by_path(r.path).last_cached_at
|
19
|
+
end
|
20
|
+
|
21
|
+
def worker
|
22
|
+
r = self
|
23
|
+
Mobilize::Resque.find_worker_by_path(r.path)
|
24
|
+
end
|
25
|
+
|
26
|
+
def Runner.find_by_email(email)
|
27
|
+
Runner.where(:email=>email).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def Runner.find_by_path(path)
|
31
|
+
Runner.where(:path=>path).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def Runner.perform(id,*args)
|
35
|
+
r = Runner.find_by_path(id)
|
36
|
+
#get gdrive slot for read
|
37
|
+
gdrive_slot = Gdrive.slot_worker_by_path(r.path)
|
38
|
+
unless gdrive_slot
|
39
|
+
r.update_status("no gdrive slot available")
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
#make sure any updates to activity are processed first
|
43
|
+
#as in when someone runs a "once" job that has completed
|
44
|
+
r.update_gsheet(gdrive_slot)
|
45
|
+
#read the jobs in the gsheet and update models with news
|
46
|
+
r.read_gsheet(gdrive_slot)
|
47
|
+
#queue up the jobs that are due and active
|
48
|
+
r.jobs.each do |j|
|
49
|
+
begin
|
50
|
+
if j.is_due?
|
51
|
+
j.tasks.first.enqueue!
|
52
|
+
end
|
53
|
+
rescue ScriptError, StandardError => exc
|
54
|
+
j.update_status("Failed to enqueue with #{exc.to_s}")
|
55
|
+
j.update_attributes(:active=>false)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
r.update_gsheet(gdrive_slot)
|
59
|
+
r.update_attributes(:last_run=>Time.now.utc)
|
60
|
+
end
|
61
|
+
|
62
|
+
def dataset
|
63
|
+
r = self
|
64
|
+
Dataset.find_or_create_by_handler_and_path("gsheet",r.path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def Runner.find_or_create_by_path(path)
|
68
|
+
Runner.where(:path=>path).first || Runner.create(:path=>path,:active=>true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def read_cache
|
72
|
+
r = self
|
73
|
+
r.dataset.read_cache
|
74
|
+
end
|
75
|
+
|
76
|
+
def gsheet(gdrive_slot)
|
77
|
+
r = self
|
78
|
+
jobs_sheet = Gsheet.find_or_create_by_path(r.path,gdrive_slot)
|
79
|
+
jobs_sheet.add_headers(r.headers)
|
80
|
+
jobs_sheet.delete_sheet1
|
81
|
+
return jobs_sheet
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_gsheet(gdrive_slot)
|
85
|
+
r = self
|
86
|
+
gsheet_tsv = r.gsheet(gdrive_slot).to_tsv
|
87
|
+
#cache in DB
|
88
|
+
r.dataset.write_cache(gsheet_tsv)
|
89
|
+
#turn it into a hash array
|
90
|
+
gsheet_jobs = gsheet_tsv.tsv_to_hash_array
|
91
|
+
#go through each job, update relevant job with its params
|
92
|
+
done_jobs = []
|
93
|
+
#parse out the jobs and update the Job collection
|
94
|
+
gsheet_jobs.each_with_index do |rj,rj_i|
|
95
|
+
#skip non-jobs or jobs without required values
|
96
|
+
next if (rj['name'].to_s.first == "#" or ['name','active','trigger','task1'].select{|c| rj[c].to_s.strip==""}.length>0)
|
97
|
+
j = Job.find_or_create_by_path("#{r.path}/#{rj['name']}")
|
98
|
+
#update top line params
|
99
|
+
j.update_attributes(:active => rj['active'],
|
100
|
+
:trigger => rj['trigger'])
|
101
|
+
(1..5).to_a.each do |t_idx|
|
102
|
+
task_string = rj["task#{t_idx.to_s}"]
|
103
|
+
break if task_string.to_s.length==0
|
104
|
+
t = Task.find_or_create_by_path("#{j.path}/task#{t_idx.to_s}")
|
105
|
+
#parse command string, update task with it
|
106
|
+
t_handler, call, param_string = [""*3]
|
107
|
+
task_string.split(" ").ie do |spls|
|
108
|
+
t_handler = spls.first.split(".").first
|
109
|
+
call = spls.first.split(".").last
|
110
|
+
param_string = spls[1..-1].join(" ").strip
|
111
|
+
end
|
112
|
+
t.update_attributes(:call=>call, :handler=>t_handler, :param_string=>param_string)
|
113
|
+
end
|
114
|
+
r.update_status("Updated #{j.path} tasks at #{Time.now.utc}")
|
115
|
+
#add this job to list of read ones
|
116
|
+
done_jobs << j
|
117
|
+
end
|
118
|
+
#delete user jobs that are not included in Runner
|
119
|
+
(r.jobs.map{|j| j.path} - done_jobs.map{|j| j.path}).each do |rj_path|
|
120
|
+
j = Job.where(:path=>rj_path).first
|
121
|
+
j.delete if j
|
122
|
+
r.update_status("Deleted job:#{rj_path}")
|
123
|
+
end
|
124
|
+
r.update_status("jobs read at #{Time.now.utc}")
|
125
|
+
return true
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_gsheet(gdrive_slot)
|
129
|
+
r = self
|
130
|
+
jobs_gsheet = r.gsheet(gdrive_slot)
|
131
|
+
upd_rows = r.jobs.map{|j| {'name'=>j.name, 'active'=>j.active, 'status'=>j.status} }
|
132
|
+
jobs_gsheet.add_or_update_rows(upd_rows)
|
133
|
+
r.update_status("gsheet updated")
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
|
137
|
+
def jobs(jname=nil)
|
138
|
+
r = self
|
139
|
+
js = Job.where(:path=>/^#{r.path.escape_regex}/).to_a
|
140
|
+
if jname
|
141
|
+
return js.sel{|j| j.name == jname}.first
|
142
|
+
else
|
143
|
+
return js
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def user
|
148
|
+
r = self
|
149
|
+
user_name = r.path.split("_").second.split("(").first.split("/").first
|
150
|
+
User.where(:email=>[user_name,Gdrive.domain].join("@")).first
|
151
|
+
end
|
152
|
+
|
153
|
+
def update_status(msg)
|
154
|
+
r = self
|
155
|
+
r.update_attributes(:status=>msg)
|
156
|
+
Mobilize::Resque.set_worker_args_by_path(r.path,{'status'=>msg})
|
157
|
+
return true
|
158
|
+
end
|
159
|
+
|
160
|
+
def is_working?
|
161
|
+
r = self
|
162
|
+
Mobilize::Resque.active_paths.include?(r.path)
|
163
|
+
end
|
164
|
+
|
165
|
+
def is_due?
|
166
|
+
r = self.reload
|
167
|
+
return false if r.is_working?
|
168
|
+
last_due_time = Time.now.utc - Jobtracker.runner_read_freq
|
169
|
+
return true if r.last_run.nil? or r.last_run < last_due_time
|
170
|
+
end
|
171
|
+
|
172
|
+
def enqueue!
|
173
|
+
r = self
|
174
|
+
::Resque::Job.create("mobilize",Runner,r.path,{})
|
175
|
+
return true
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|