mobilize-base 1.29 → 1.33

Sign up to get free protection for your applications and to get access to all the features.
@@ -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?