mobilize-base 1.29 → 1.33

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.
@@ -0,0 +1,147 @@
1
+ module Mobilize
2
+ module Jobtracker
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
+ #this is to keep jobtracker from resisting stop commands
52
+ return false if Jobtracker.status=="stopping"
53
+ #Jobtracker has no persistent database state
54
+ Resque.set_worker_args_by_path("jobtracker",{'status'=>msg})
55
+ return true
56
+ end
57
+
58
+ def Jobtracker.restart
59
+ Jobtracker.stop!
60
+ Jobtracker.start
61
+ end
62
+
63
+ def Jobtracker.set_args(args)
64
+ Resque.set_worker_args(Jobtracker.worker,args)
65
+ return true
66
+ end
67
+
68
+ def Jobtracker.get_args
69
+ Resque.get_worker_args(Jobtracker.worker)
70
+ end
71
+
72
+ def Jobtracker.kill_workers
73
+ Resque.kill_workers
74
+ end
75
+
76
+ def Jobtracker.kill_idle_workers
77
+ Resque.kill_idle_workers
78
+ end
79
+
80
+ def Jobtracker.kill_idle_and_stale_workers
81
+ Resque.kill_idle_and_stale_workers
82
+ end
83
+
84
+ def Jobtracker.prep_workers
85
+ Resque.prep_workers
86
+ end
87
+
88
+ def Jobtracker.failures
89
+ Resque.failures
90
+ end
91
+
92
+ def Jobtracker.start
93
+ if Jobtracker.status!='stopped'
94
+ Jobtracker.update_status("Jobtracker still #{Jobtracker.status}")
95
+ else
96
+ #make sure that workers are running and at the right number
97
+ #Resque.prep_workers
98
+ #queue up the jobtracker (starts the perform method)
99
+ Jobtracker.enqueue!
100
+ end
101
+ return true
102
+ end
103
+
104
+ def Jobtracker.enqueue!
105
+ ::Resque::Job.create(Resque.queue_name, Jobtracker, 'jobtracker',{})
106
+ end
107
+
108
+ def Jobtracker.restart!
109
+ Jobtracker.stop!
110
+ Jobtracker.start
111
+ return true
112
+ end
113
+
114
+ def Jobtracker.restart_workers!
115
+ Jobtracker.kill_workers
116
+ sleep 10
117
+ Jobtracker.prep_workers
118
+ Jobtracker.update_status("put workers back on the queue")
119
+ end
120
+
121
+ def Jobtracker.stop!
122
+ #send signal for Jobtracker to check for
123
+ Jobtracker.update_status('stopping')
124
+ sleep 5
125
+ i=0
126
+ while Jobtracker.status=='stopping'
127
+ puts "#{Jobtracker.to_s} still on queue, waiting"
128
+ sleep 5
129
+ i+=1
130
+ end
131
+ return true
132
+ end
133
+
134
+ def Jobtracker.last_notification
135
+ return Jobtracker.get_args["last_notification"] if Jobtracker.get_args
136
+ end
137
+
138
+ def Jobtracker.last_notification=(time)
139
+ Jobtracker.set_args({"last_notification"=>time})
140
+ end
141
+
142
+ def Jobtracker.notif_due?
143
+ last_duetime = Time.now.utc - Jobtracker.notification_freq
144
+ return (Jobtracker.last_notification.to_s.length==0 || Jobtracker.last_notification.to_datetime < last_duetime)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,83 @@
1
+ #this module adds convenience methods to the Runner model
2
+ module Mobilize
3
+ module RunnerHelper
4
+ def headers
5
+ %w{name active trigger status stage1 stage2 stage3 stage4 stage5}
6
+ end
7
+
8
+ def title
9
+ r = self
10
+ r.path.split("/").first
11
+ end
12
+
13
+ def worker
14
+ r = self
15
+ Mobilize::Resque.find_worker_by_path(r.path)
16
+ end
17
+
18
+ def dataset
19
+ r = self
20
+ Dataset.find_or_create_by_handler_and_path("gsheet",r.path)
21
+ end
22
+
23
+ def gbook(gdrive_slot)
24
+ r = self
25
+ title = r.path.split("/").first
26
+ Gbook.find_by_path(title,gdrive_slot)
27
+ end
28
+
29
+ def gsheet(gdrive_slot)
30
+ r = self
31
+ u = r.user
32
+ jobs_sheet = Gsheet.find_by_path(r.path,gdrive_slot)
33
+ #make sure the user has a runner with a jobs sheet and has write privileges on the spreadsheet
34
+ unless (jobs_sheet and jobs_sheet.spreadsheet.acl_entry(u.email).ie{|e| e and e.role=="writer"})
35
+ #only give the user edit permissions if they're the ones
36
+ #creating it
37
+ jobs_sheet = Gsheet.find_or_create_by_path(r.path,gdrive_slot)
38
+ unless jobs_sheet.spreadsheet.acl_entry(u.email).ie{|e| e and e.role=="owner"}
39
+ jobs_sheet.spreadsheet.update_acl(u.email,"writer")
40
+ end
41
+ jobs_sheet.add_headers(r.headers)
42
+ begin;jobs_sheet.delete_sheet1;rescue;end #don't care if sheet1 deletion fails
43
+ end
44
+ return jobs_sheet
45
+ end
46
+
47
+ def jobs(jname=nil)
48
+ r = self
49
+ js = Job.where(:path=>/^#{r.path.escape_regex}/).to_a
50
+ if jname
51
+ return js.sel{|j| j.name == jname}.first
52
+ else
53
+ return js
54
+ end
55
+ end
56
+
57
+ def user
58
+ r = self
59
+ user_name = r.path.split("_")[1..-1].join("_").split("(").first.split("/").first
60
+ User.where(:name=>user_name).first
61
+ end
62
+
63
+ def update_status(msg)
64
+ r = self
65
+ r.update_attributes(:status=>msg, :status_at=>Time.now.utc)
66
+ Mobilize::Resque.set_worker_args_by_path(r.path,{'status'=>msg})
67
+ return true
68
+ end
69
+
70
+ def is_working?
71
+ r = self
72
+ Mobilize::Resque.active_paths.include?(r.path)
73
+ end
74
+
75
+ def is_due?
76
+ r = self.reload
77
+ return false if r.is_working?
78
+ prev_due_time = Time.now.utc - Jobtracker.runner_read_freq
79
+ return true if r.started_at.nil? or r.started_at < prev_due_time
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,38 @@
1
+ #this module adds convenience methods to the Stage model
2
+ module Mobilize
3
+ module StageHelper
4
+ def idx
5
+ s = self
6
+ s.path.split("/").last.gsub("stage","").to_i
7
+ end
8
+
9
+ def out_dst
10
+ #this gives a dataset that points to the output
11
+ #allowing you to determine its size
12
+ #before committing to a read or write
13
+ s = self
14
+ Dataset.find_by_url(s.response['out_url']) if s.response and s.response['out_url']
15
+ end
16
+
17
+ def err_dst
18
+ #this gives a dataset that points to the output
19
+ #allowing you to determine its size
20
+ #before committing to a read or write
21
+ s = self
22
+ Dataset.find_by_url(s.response['err_url']) if s.response and s.response['err_url']
23
+ end
24
+
25
+ def params
26
+ s = self
27
+ p = YAML.easy_load(s.param_string)
28
+ raise "Must resolve to Hash" unless p.class==Hash
29
+ return p
30
+ end
31
+
32
+ def job
33
+ s = self
34
+ job_path = s.path.split("/")[0..-2].join("/")
35
+ Job.where(:path=>job_path).first
36
+ end
37
+ end
38
+ end
@@ -1,152 +1,13 @@
1
1
  module Mobilize
2
2
  module Jobtracker
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
3
+ #adds convenience methods
4
+ require "#{File.dirname(__FILE__)}/helpers/jobtracker_helper"
144
5
 
145
6
  def Jobtracker.max_run_time_workers
146
7
  #return workers who have been cranking away for 6+ hours
147
8
  workers = Jobtracker.workers('working').select do |w|
148
- w.job['runat'].to_s.length>0 and
149
- (Time.now.utc - Time.parse(w.job['runat'])) > Jobtracker.max_run_time
9
+ w.job['run_at'].to_s.length>0 and
10
+ (Time.now.utc - Time.parse(w.job['run_at'])) > Jobtracker.max_run_time
150
11
  end
151
12
  return workers
152
13
  end
@@ -183,15 +44,25 @@ module Mobilize
183
44
  end
184
45
  lws = Jobtracker.max_run_time_workers
185
46
  if lws.length>0
47
+ bod = begin
48
+ lws.map{|w| w.job['payload']['args']}.first.join("\n")
49
+ rescue
50
+ "Failed to get job names"
51
+ end
186
52
  n = {}
187
53
  n['subject'] = "#{lws.length.to_s} max run time jobs"
188
- n['body'] = lws.map{|w| %{spec:#{w['spec']} stg:#{w['stg']} runat:#{w['runat'].to_s}}}.join("\n\n")
54
+ n['body'] = bod
189
55
  n['to'] = Jobtracker.admin_emails.join(",")
190
56
  notifs << n
191
57
  end
192
58
  #deliver each email generated
193
59
  notifs.each do |notif|
194
- Email.write(notif).deliver
60
+ begin
61
+ Email.write(notif).deliver
62
+ rescue
63
+ #log email on failure
64
+ Jobtracker.update_status("Failed to deliver #{notif.to_s}")
65
+ end
195
66
  end
196
67
  #update notification time so JT knows to wait a while
197
68
  Jobtracker.last_notification = Time.now.utc.to_s
@@ -243,52 +114,5 @@ module Mobilize
243
114
  end.to_s.strip
244
115
  Time.parse(deploy_time)
245
116
  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
293
117
  end
294
118
  end
@@ -2,66 +2,54 @@ module Mobilize
2
2
  class Job
3
3
  include Mongoid::Document
4
4
  include Mongoid::Timestamps
5
+ include Mobilize::JobHelper
5
6
  field :path, type: String
6
7
  field :active, type: Boolean
7
8
  field :trigger, type: String
8
9
 
9
10
  index({ path: 1})
10
11
 
11
- def name
12
- j = self
13
- j.path.split("/").last
14
- end
15
-
16
- def stages
17
- j = self
18
- #starts with the job path, followed by a slash
19
- Stage.where(:path=>/^#{j.path.escape_regex}\//).to_a.sort_by{|s| s.path}
20
- end
21
-
22
12
  def Job.find_or_create_by_path(path)
23
13
  j = Job.where(:path=>path).first
24
14
  j = Job.create(:path=>path) unless j
25
15
  return j
26
16
  end
27
17
 
28
- def status
29
- #last stage status
30
- j = self
31
- j.active_stage.status if j.active_stage
32
- end
33
-
34
- def active_stage
35
- j = self
36
- #latest started at or first
37
- j.stages.select{|s| s.started_at}.sort_by{|s| s.started_at}.last || j.stages.first
38
- end
39
-
40
- def completed_at
41
- j = self
42
- j.stages.last.completed_at if j.stages.last
43
- end
44
-
45
- def failed_at
46
- j = self
47
- j.active_stage.failed_at if j.active_stage
48
- end
49
-
50
- def status_at
51
- j = self
52
- j.active_stage.status_at if j.active_stage
53
- end
54
-
55
- #convenience methods
56
- def runner
57
- j = self
58
- runner_path = j.path.split("/")[0..-2].join("/")
59
- return Runner.where(:path=>runner_path).first
60
- end
61
-
62
- def is_working?
63
- j = self
64
- j.stages.select{|s| s.is_working?}.compact.length>0
18
+ #takes a hash of job parameters (name, active, trigger, stages)
19
+ #and creates/updates a job with it
20
+ def Job.update_by_user_name_and_hash(user_name,hash)
21
+ u = User.where(name: user_name).first
22
+ r = u.runner
23
+ j = Job.find_or_create_by_path("#{r.path}/#{hash['name']}")
24
+ #update top line params
25
+ j.update_attributes(:active => hash['active'],
26
+ :trigger => hash['trigger'])
27
+ (1..5).to_a.each do |s_idx|
28
+ stage_string = hash["stage#{s_idx.to_s}"]
29
+ s = Stage.find_by_path("#{j.path}/stage#{s_idx.to_s}")
30
+ if stage_string.to_s.length==0
31
+ #delete this stage and all stages after
32
+ if s
33
+ j = s.job
34
+ j.stages[(s.idx-1)..-1].each{|ps| ps.delete}
35
+ #just in case
36
+ s.delete
37
+ end
38
+ break
39
+ elsif s.nil?
40
+ #create this stage
41
+ s = Stage.find_or_create_by_path("#{j.path}/stage#{s_idx.to_s}")
42
+ end
43
+ #parse command string, update stage with it
44
+ s_handler, call, param_string = [""*3]
45
+ stage_string.split(" ").ie do |spls|
46
+ s_handler = spls.first.split(".").first
47
+ call = spls.first.split(".").last
48
+ param_string = spls[1..-1].join(" ").strip
49
+ end
50
+ s.update_attributes(:call=>call, :handler=>s_handler, :param_string=>param_string)
51
+ end
52
+ return j.reload
65
53
  end
66
54
 
67
55
  def is_due?