right_chimp 1.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 +7 -0
- data/CHANGES +88 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +49 -0
- data/INSTALL +25 -0
- data/LICENSE +20 -0
- data/README +80 -0
- data/Rakefile +18 -0
- data/bin/chimp +12 -0
- data/bin/chimpd +12 -0
- data/chimp.gemspec +27 -0
- data/lib/right_chimp/Chimp.rb +1181 -0
- data/lib/right_chimp/IDManager.rb +18 -0
- data/lib/right_chimp/Log.rb +34 -0
- data/lib/right_chimp/daemon/ChimpDaemon.rb +416 -0
- data/lib/right_chimp/daemon/ChimpDaemonClient.rb +74 -0
- data/lib/right_chimp/exec/ExecArray.rb +43 -0
- data/lib/right_chimp/exec/ExecCallback.rb +15 -0
- data/lib/right_chimp/exec/ExecNoop.rb +9 -0
- data/lib/right_chimp/exec/ExecReport.rb +57 -0
- data/lib/right_chimp/exec/ExecRightScript.rb +35 -0
- data/lib/right_chimp/exec/ExecSSH.rb +45 -0
- data/lib/right_chimp/exec/Executor.rb +180 -0
- data/lib/right_chimp/queue/ChimpQueue.rb +187 -0
- data/lib/right_chimp/queue/ExecutionGroup.rb +277 -0
- data/lib/right_chimp/queue/QueueWorker.rb +42 -0
- data/lib/right_chimp/templates/all_jobs.erb +214 -0
- data/lib/right_chimp/version.rb +3 -0
- data/lib/right_chimp.rb +32 -0
- data/spec/spec_chimp.rb +20 -0
- data/spec/spec_chimp_commandline.rb +55 -0
- data/spec/spec_chimpd.rb +33 -0
- data/spec/spec_chimpd_client.rb +44 -0
- data/spec/spec_execution_group.rb +135 -0
- data/spec/spec_queue.rb +37 -0
- data/spec/spec_queue_worker.rb +30 -0
- data/spec/spec_selection.rb +33 -0
- metadata +159 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Utility class for generating sequential job ids
|
|
3
|
+
#
|
|
4
|
+
module Chimp
|
|
5
|
+
class IDManager
|
|
6
|
+
@@id = 0
|
|
7
|
+
@@mutex = Mutex.new
|
|
8
|
+
|
|
9
|
+
def self.get
|
|
10
|
+
r = nil
|
|
11
|
+
@@mutex.synchronize do
|
|
12
|
+
@@id = @@id + 1
|
|
13
|
+
r = @@id
|
|
14
|
+
end
|
|
15
|
+
return r
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Chimp
|
|
2
|
+
class Log
|
|
3
|
+
@@logger = Logger.new(STDOUT)
|
|
4
|
+
@@logger.sev_threshold=Logger::INFO
|
|
5
|
+
|
|
6
|
+
def self.logger=(l)
|
|
7
|
+
@@logger = l
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.threshold=(level)
|
|
11
|
+
@@logger.sev_threshold=level
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.default_level(level)
|
|
15
|
+
@@logger.level = level
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.debug(m)
|
|
19
|
+
@@logger.debug(m)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.info(m)
|
|
23
|
+
@@logger.info(m)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.warn(m)
|
|
27
|
+
@@logger.warn(m)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.error(m)
|
|
31
|
+
@@logger.error(m)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
#
|
|
2
|
+
# ChimpDaemon.rb
|
|
3
|
+
#
|
|
4
|
+
# Classes for the Chimp Daemon (chimpd)
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
module Chimp
|
|
8
|
+
class ChimpDaemon
|
|
9
|
+
attr_accessor :verbose, :debug, :port, :concurrency, :delay, :retry_count, :dry_run, :logfile
|
|
10
|
+
attr_reader :queue, :running
|
|
11
|
+
|
|
12
|
+
include Singleton
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@verbose = false
|
|
16
|
+
@debug = false
|
|
17
|
+
@port = 9055
|
|
18
|
+
@concurrency = 50
|
|
19
|
+
@delay = 0
|
|
20
|
+
@retry_count = 0
|
|
21
|
+
@threads = []
|
|
22
|
+
@running = false
|
|
23
|
+
@queue = ChimpQueue.instance
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Main entry point for chimpd command line application
|
|
28
|
+
#
|
|
29
|
+
def run
|
|
30
|
+
install_signal_handlers
|
|
31
|
+
parse_command_line
|
|
32
|
+
|
|
33
|
+
puts "chimpd #{VERSION} launching with #{@concurrency} workers"
|
|
34
|
+
spawn_queue_runner
|
|
35
|
+
spawn_webserver
|
|
36
|
+
run_forever
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# Parse chimpd command line options
|
|
41
|
+
#
|
|
42
|
+
def parse_command_line
|
|
43
|
+
begin
|
|
44
|
+
opts = GetoptLong.new(
|
|
45
|
+
[ '--logfile', '-l', GetoptLong::REQUIRED_ARGUMENT ],
|
|
46
|
+
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
|
|
47
|
+
[ '--quiet', '-q', GetoptLong::NO_ARGUMENT ],
|
|
48
|
+
[ '--concurrency', '-c', GetoptLong::REQUIRED_ARGUMENT ],
|
|
49
|
+
[ '--delay', '-d', GetoptLong::REQUIRED_ARGUMENT ],
|
|
50
|
+
[ '--retry', '-y', GetoptLong::REQUIRED_ARGUMENT ],
|
|
51
|
+
[ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT ],
|
|
52
|
+
[ '--exit', '-x', GetoptLong::NO_ARGUMENT ]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
opts.each do |opt, arg|
|
|
56
|
+
case opt
|
|
57
|
+
when '--logfile', '-l'
|
|
58
|
+
@logfile = arg
|
|
59
|
+
Log.logger = Logger.new(@logfile)
|
|
60
|
+
when '--concurrency', '-c'
|
|
61
|
+
@concurrency = arg.to_i
|
|
62
|
+
when '--delay', '-d'
|
|
63
|
+
@delay = arg.to_i
|
|
64
|
+
when '--retry', '-y'
|
|
65
|
+
@retry_count = arg.to_i
|
|
66
|
+
when '--verbose', '-v'
|
|
67
|
+
@verbose = true
|
|
68
|
+
when '--quiet', '-q'
|
|
69
|
+
@quiet = true
|
|
70
|
+
when '--port', '-p'
|
|
71
|
+
@port = arg
|
|
72
|
+
when '--exit', '-x'
|
|
73
|
+
uri = "http://localhost:9055/admin"
|
|
74
|
+
response = RestClient.post uri, { 'shutdown' => true }.to_yaml
|
|
75
|
+
exit 0
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
rescue GetoptLong::InvalidOption => ex
|
|
79
|
+
puts "Syntax: chimpd [--logfile=<name>] [--concurrency=<c>] [--delay=<d>] [--retry=<r>] [--port=<p>] [--verbose]"
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Set up logging/verbosity
|
|
85
|
+
#
|
|
86
|
+
Chimp.set_verbose(@verbose, @quiet)
|
|
87
|
+
|
|
88
|
+
if not @verbose
|
|
89
|
+
ENV['REST_CONNECTION_LOG'] = "/dev/null"
|
|
90
|
+
ENV['RESTCLIENT_LOG'] = "/dev/null"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Spawn the ChimpQueue threads
|
|
96
|
+
#
|
|
97
|
+
def spawn_queue_runner
|
|
98
|
+
@queue.max_threads = @concurrency
|
|
99
|
+
@queue.delay = @delay
|
|
100
|
+
@queue.retry_count = @retry_count
|
|
101
|
+
@queue.start
|
|
102
|
+
@running = true
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Spawn a WEBrick Web server
|
|
107
|
+
#
|
|
108
|
+
def spawn_webserver
|
|
109
|
+
opts = {
|
|
110
|
+
:BindAddress => "localhost",
|
|
111
|
+
:Port => @port,
|
|
112
|
+
:MaxClients => 50,
|
|
113
|
+
:RequestTimeout => 60
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if not @verbose
|
|
117
|
+
opts[:Logger] = WEBrick::Log.new("/dev/null")
|
|
118
|
+
opts[:AccessLog] = [nil, nil]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@server = ::WEBrick::HTTPServer.new(opts)
|
|
122
|
+
@server.mount('/', DisplayServlet)
|
|
123
|
+
@server.mount('/display', DisplayServlet)
|
|
124
|
+
@server.mount('/job', JobServlet)
|
|
125
|
+
@server.mount('/group', GroupServlet)
|
|
126
|
+
@server.mount('/admin', AdminServlet)
|
|
127
|
+
|
|
128
|
+
#
|
|
129
|
+
# WEBrick threads
|
|
130
|
+
#
|
|
131
|
+
@threads << Thread.new(1001) do
|
|
132
|
+
@server.start
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
#
|
|
137
|
+
# Process requests forever until we're killed
|
|
138
|
+
#
|
|
139
|
+
def run_forever
|
|
140
|
+
@running = true
|
|
141
|
+
while @running
|
|
142
|
+
@threads.each do |t|
|
|
143
|
+
t.join(5)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
#
|
|
149
|
+
# Trap signals to exit cleanly
|
|
150
|
+
#
|
|
151
|
+
def install_signal_handlers
|
|
152
|
+
['INT', 'TERM'].each do |signal|
|
|
153
|
+
trap(signal) do
|
|
154
|
+
self.quit
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# Quit by waiting for all chimp jobs to finish, not allowing
|
|
161
|
+
# new jobs on the queue, and killing the web server.
|
|
162
|
+
#
|
|
163
|
+
# TODO: call @queue.quit, but with a short timeout?
|
|
164
|
+
#
|
|
165
|
+
def quit
|
|
166
|
+
@running = false
|
|
167
|
+
@server.shutdown
|
|
168
|
+
sleep 5
|
|
169
|
+
exit 0
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
#
|
|
173
|
+
# GenericServlet -- servlet superclass
|
|
174
|
+
#
|
|
175
|
+
class GenericServlet < WEBrick::HTTPServlet::AbstractServlet
|
|
176
|
+
def get_verb(req)
|
|
177
|
+
r = req.request_uri.path.split('/')[2]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def get_id(req)
|
|
181
|
+
uri_parts = req.request_uri.path.split('/')
|
|
182
|
+
id = uri_parts[-2]
|
|
183
|
+
return id
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Get the body of the request-- assume YAML
|
|
188
|
+
#
|
|
189
|
+
def get_payload(req)
|
|
190
|
+
begin
|
|
191
|
+
return YAML::load(req.body)
|
|
192
|
+
rescue StandardError => ex
|
|
193
|
+
return nil
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end # GenericServlet
|
|
197
|
+
|
|
198
|
+
#
|
|
199
|
+
# AdminServlet - admin functions
|
|
200
|
+
#
|
|
201
|
+
class AdminServlet < GenericServlet
|
|
202
|
+
def do_POST(req, resp)
|
|
203
|
+
payload = self.get_payload(req)
|
|
204
|
+
shutdown = payload['shutdown'] || false
|
|
205
|
+
|
|
206
|
+
if shutdown == true
|
|
207
|
+
ChimpDaemon.instance.quit
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
raise WEBrick::HTTPStatus::OK
|
|
211
|
+
end
|
|
212
|
+
end # AdminServlet
|
|
213
|
+
|
|
214
|
+
#
|
|
215
|
+
# GroupServlet - group information and control
|
|
216
|
+
#
|
|
217
|
+
# http://localhost:9055/group/default/running
|
|
218
|
+
#
|
|
219
|
+
class GroupServlet < GenericServlet
|
|
220
|
+
#
|
|
221
|
+
# GET a group by name and status
|
|
222
|
+
# /group/<name>/<status>
|
|
223
|
+
#
|
|
224
|
+
def do_GET(req, resp)
|
|
225
|
+
jobs = {}
|
|
226
|
+
|
|
227
|
+
group_name = req.request_uri.path.split('/')[-2]
|
|
228
|
+
filter = req.request_uri.path.split('/')[-1]
|
|
229
|
+
|
|
230
|
+
g = ChimpQueue[group_name.to_sym]
|
|
231
|
+
raise WEBrick::HTTPStatus::NotFound, "Group not found" unless g
|
|
232
|
+
jobs = g.get_jobs_by_status(filter)
|
|
233
|
+
resp.body = jobs.to_yaml
|
|
234
|
+
raise WEBrick::HTTPStatus::OK
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
#
|
|
238
|
+
# POST to a group to trigger a group action
|
|
239
|
+
# /group/<name>/<action>
|
|
240
|
+
#
|
|
241
|
+
def do_POST(req, resp)
|
|
242
|
+
group_name = req.request_uri.path.split('/')[-2]
|
|
243
|
+
filter = req.request_uri.path.split('/')[-1]
|
|
244
|
+
payload = self.get_payload(req)
|
|
245
|
+
|
|
246
|
+
if filter == 'create'
|
|
247
|
+
ChimpQueue.instance.create_group(group_name, payload['type'], payload['concurrency'])
|
|
248
|
+
|
|
249
|
+
elsif filter == 'retry'
|
|
250
|
+
group = ChimpQueue[group_name.to_sym]
|
|
251
|
+
raise WEBrick::HTTPStatus::NotFound, "Group not found" unless group
|
|
252
|
+
|
|
253
|
+
group.requeue_failed_jobs!
|
|
254
|
+
raise WEBrick::HTTPStatus::OK
|
|
255
|
+
|
|
256
|
+
else
|
|
257
|
+
raise WEBrick::HTTPStatus::PreconditionFailed.new("invalid action")
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
#
|
|
264
|
+
# JobServlet - job control
|
|
265
|
+
#
|
|
266
|
+
# HTTP body is a yaml serialized chimp object
|
|
267
|
+
#
|
|
268
|
+
class JobServlet < GenericServlet
|
|
269
|
+
def do_POST(req, resp)
|
|
270
|
+
id = -1
|
|
271
|
+
job_id = self.get_id(req)
|
|
272
|
+
verb = self.get_verb(req)
|
|
273
|
+
|
|
274
|
+
payload = self.get_payload(req)
|
|
275
|
+
raise WEBrick::HTTPStatus::PreconditionFailed.new('missing payload') unless payload
|
|
276
|
+
|
|
277
|
+
q = ChimpQueue.instance
|
|
278
|
+
group = payload.group
|
|
279
|
+
|
|
280
|
+
#
|
|
281
|
+
# Ask chimpd to process a Chimp object directly
|
|
282
|
+
#
|
|
283
|
+
if verb == 'process' or verb == 'add'
|
|
284
|
+
payload.interactive = false
|
|
285
|
+
tasks = payload.process
|
|
286
|
+
tasks.each do |task|
|
|
287
|
+
q.push(group, task)
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
elsif verb == 'update'
|
|
291
|
+
puts "UPDATE"
|
|
292
|
+
q.get_job(job_id).status = payload.status
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
resp.body = {
|
|
296
|
+
'id' => id
|
|
297
|
+
}.to_yaml
|
|
298
|
+
|
|
299
|
+
raise WEBrick::HTTPStatus::OK
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def do_GET(req, resp)
|
|
303
|
+
id = self.get_id(req)
|
|
304
|
+
verb = self.get_verb(req)
|
|
305
|
+
job_results = "OK"
|
|
306
|
+
queue = ChimpQueue.instance
|
|
307
|
+
|
|
308
|
+
#
|
|
309
|
+
# check for special job ids
|
|
310
|
+
#
|
|
311
|
+
jobs = []
|
|
312
|
+
jobs << queue.get_job(id.to_i)
|
|
313
|
+
|
|
314
|
+
jobs = queue.get_jobs_by_status(:running) if id == 'running'
|
|
315
|
+
jobs = queue.get_jobs_by_status(:error) if id == 'error'
|
|
316
|
+
jobs = queue.get_jobs if id == 'all'
|
|
317
|
+
|
|
318
|
+
raise WEBrick::HTTPStatus::PreconditionFailed.new('invalid or missing job_id #{id}') unless jobs.size > 0
|
|
319
|
+
|
|
320
|
+
#
|
|
321
|
+
# ACK a job -- mark it as successful even if it failed
|
|
322
|
+
#
|
|
323
|
+
if req.request_uri.path =~ /ack$/
|
|
324
|
+
jobs.each do |j|
|
|
325
|
+
j.status = Executor::STATUS_DONE
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
resp.set_redirect( WEBrick::HTTPStatus::TemporaryRedirect, req.header['referer'])
|
|
329
|
+
|
|
330
|
+
#
|
|
331
|
+
# retry a job
|
|
332
|
+
#
|
|
333
|
+
elsif req.request_uri.path =~ /retry$/
|
|
334
|
+
jobs.each do |j|
|
|
335
|
+
j.requeue
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
resp.set_redirect( WEBrick::HTTPStatus::TemporaryRedirect, req.header['referer'])
|
|
339
|
+
|
|
340
|
+
#
|
|
341
|
+
# cancel an active job
|
|
342
|
+
#
|
|
343
|
+
elsif req.request_uri.path =~ /cancel$/
|
|
344
|
+
jobs.each do |j|
|
|
345
|
+
j.cancel if j.respond_to? :cancel
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
resp.set_redirect( WEBrick::HTTPStatus::TemporaryRedirect, req.header['referer'])
|
|
349
|
+
|
|
350
|
+
#
|
|
351
|
+
# produce a report
|
|
352
|
+
#
|
|
353
|
+
elsif req.request_uri.path =~ /report$/
|
|
354
|
+
results = ["group_name,type,job_id,script,target,start_time,end_time,total_time,status"]
|
|
355
|
+
jobs.each do |j|
|
|
356
|
+
results << [j.group.group_id, j.class.to_s.sub("Chimp::",""), j.job_id, j.info, j.target, j.time_start, j.time_end, j.get_total_exec_time, j.status].join(",")
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
queue.group.values.each do |g|
|
|
360
|
+
results << [g.group_id, g.class.to_s.sub("Chimp::",""), "", "", "", g.time_start, g.time_end, g.get_total_exec_time, ""].join(",")
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
job_results = results.join("\n") + "\n"
|
|
364
|
+
|
|
365
|
+
resp['Content-type'] = "text/csv"
|
|
366
|
+
resp['Content-disposition'] = "attachment;filename=chimp.csv"
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
#
|
|
370
|
+
# return a list of the results
|
|
371
|
+
#
|
|
372
|
+
resp.body = job_results
|
|
373
|
+
raise WEBrick::HTTPStatus::OK
|
|
374
|
+
end
|
|
375
|
+
end # JobServlet
|
|
376
|
+
|
|
377
|
+
#
|
|
378
|
+
# DisplayServlet
|
|
379
|
+
#
|
|
380
|
+
class DisplayServlet < GenericServlet
|
|
381
|
+
def do_GET(req, resp)
|
|
382
|
+
job_filter = self.get_verb(req) || "running"
|
|
383
|
+
|
|
384
|
+
if not @template
|
|
385
|
+
if ENV['CHIMP_TEST'] != 'TRUE'
|
|
386
|
+
template_file_name = File.join(Gem.dir, 'gems', 'chimp-' + VERSION, 'lib/chimp/templates/all_jobs.erb')
|
|
387
|
+
else
|
|
388
|
+
template_file_name = 'lib/chimp/templates/all_jobs.erb'
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
@template = ERB.new(File.read(template_file_name), nil, ">")
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
queue = ChimpQueue.instance
|
|
395
|
+
jobs = queue.get_jobs
|
|
396
|
+
group_name = nil
|
|
397
|
+
|
|
398
|
+
if job_filter == "group"
|
|
399
|
+
group_name = req.request_uri.path.split('/')[-1]
|
|
400
|
+
g = ChimpQueue[group_name.to_sym]
|
|
401
|
+
jobs = g.get_jobs if g
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
count_jobs_running = queue.get_jobs_by_status(:running).size
|
|
405
|
+
count_jobs_queued = queue.get_jobs_by_status(:none).size
|
|
406
|
+
count_jobs_failed = queue.get_jobs_by_status(:error).size
|
|
407
|
+
count_jobs_done = queue.get_jobs_by_status(:done).size
|
|
408
|
+
|
|
409
|
+
resp.body = @template.result(binding)
|
|
410
|
+
raise WEBrick::HTTPStatus::OK
|
|
411
|
+
end
|
|
412
|
+
end # DisplayServlet
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Chimp
|
|
2
|
+
#
|
|
3
|
+
# The ChimpDaemonClient contains the code for communicating with the
|
|
4
|
+
# RESTful chimpd Web service
|
|
5
|
+
#
|
|
6
|
+
class ChimpDaemonClient
|
|
7
|
+
#
|
|
8
|
+
# Submit a Chimp object to a remote chimpd
|
|
9
|
+
#
|
|
10
|
+
def self.submit(host, port, chimp_object)
|
|
11
|
+
uri = "http://#{host}:#{port}/job/process"
|
|
12
|
+
response = RestClient.post uri, chimp_object.to_yaml
|
|
13
|
+
|
|
14
|
+
if response.code > 199 and response.code < 300
|
|
15
|
+
begin
|
|
16
|
+
id = YAML::load(response.body)['id']
|
|
17
|
+
rescue StandardError => ex
|
|
18
|
+
puts ex
|
|
19
|
+
end
|
|
20
|
+
else
|
|
21
|
+
$stderr.puts "error submitting to chimpd! response code: #{reponse.code}"
|
|
22
|
+
return false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
puts "chimpd submission complete"
|
|
26
|
+
return true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
#
|
|
30
|
+
# quit a remote chimpd
|
|
31
|
+
#
|
|
32
|
+
def self.quit(host, port)
|
|
33
|
+
response = RestClient.post "http://#{host}:#{port}/admin", { 'shutdown' => 'true' }.to_yaml
|
|
34
|
+
return response.code
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# retrieve job info from a remote chimpd
|
|
39
|
+
#
|
|
40
|
+
def self.retrieve_job_info(host, port)
|
|
41
|
+
uri = "http://#{host}:#{port}/job/0"
|
|
42
|
+
response = RestClient.get uri
|
|
43
|
+
jobs = YAML::load(response.body)
|
|
44
|
+
return jobs
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.retrieve_group_info(host, port, group_name, status)
|
|
48
|
+
uri = "http://#{host}:#{port}/group/#{group_name}/#{status}"
|
|
49
|
+
response = RestClient.get uri
|
|
50
|
+
group = YAML::load(response.body)
|
|
51
|
+
return group
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.set_job_status(host, port, job_id, status)
|
|
55
|
+
uri = "http://#{host}:#{port}/job/#{job_id}/update"
|
|
56
|
+
response = RestClient.post uri, { :status => status}
|
|
57
|
+
return YAML::load(response.body)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.create_group(host, port, name, type, concurrency)
|
|
61
|
+
uri = "http://#{host}:#{port}/group/#{name}/create"
|
|
62
|
+
payload = { 'type' => type, 'concurrency' => concurrency }.to_yaml
|
|
63
|
+
response = RestClient.post(uri, payload)
|
|
64
|
+
return YAML::load(response.body)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def self.retry_group(host, port, group_name)
|
|
68
|
+
uri = "http://#{host}:#{port}/group/#{group_name}/retry"
|
|
69
|
+
response = RestClient.post(uri, {})
|
|
70
|
+
return YAML::load(response.body)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Run a RightScript on an array
|
|
3
|
+
#
|
|
4
|
+
module Chimp
|
|
5
|
+
class ExecArray < Executor
|
|
6
|
+
def run
|
|
7
|
+
run_with_retry do
|
|
8
|
+
audit_entry = []
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# If we have API 0.1 access, then use the 0.1 version of this call.
|
|
12
|
+
# Otherwise use the 1.0 version.
|
|
13
|
+
#
|
|
14
|
+
if @array.internal
|
|
15
|
+
audit_entry = @array.internal.run_script_on_instances(@exec, @server['href'], {})
|
|
16
|
+
else
|
|
17
|
+
audit_entry = @array.run_script_on_instances(@exec, @server['href'], {})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if audit_entry
|
|
21
|
+
audit_entry.each do |a|
|
|
22
|
+
a.wait_for_completed
|
|
23
|
+
end
|
|
24
|
+
else
|
|
25
|
+
Log.warn "No audit entries returned for job_id=#{@job_id}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def describe_work
|
|
31
|
+
return "ExecArray job_id=#{@job_id} script=\"#{@exec['right_script']['name']}\" server=\"#{@server['nickname']}\""
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def info
|
|
35
|
+
return @exec['right_script']['name']
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def target
|
|
39
|
+
return @server['nickname']
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Report
|
|
3
|
+
#
|
|
4
|
+
# :fields is a comma seperated list of fields to report on,
|
|
5
|
+
# and are taken from the RestConnection Server objects directly.
|
|
6
|
+
#
|
|
7
|
+
# Example: ip-address,name,href,private-ip-address,aws-id,ec2-instance-type,dns-name,locked
|
|
8
|
+
#
|
|
9
|
+
module Chimp
|
|
10
|
+
class ExecReport < Executor
|
|
11
|
+
attr_reader :server, :fields
|
|
12
|
+
attr_writer :server, :fields
|
|
13
|
+
|
|
14
|
+
def info
|
|
15
|
+
return "report on server #{fields.inspect}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
run_with_retry do
|
|
20
|
+
output = []
|
|
21
|
+
|
|
22
|
+
#
|
|
23
|
+
# The API and rest_connection return very different data
|
|
24
|
+
# depending on the API call made (here, tag query vs. array)
|
|
25
|
+
# so the logic to load the server information and tags is
|
|
26
|
+
# messy.
|
|
27
|
+
#
|
|
28
|
+
begin
|
|
29
|
+
s = Server.find(@server.href)
|
|
30
|
+
s.settings
|
|
31
|
+
response = ::Tag.search_by_href(s.current_instance_href)
|
|
32
|
+
rescue Exception => ex
|
|
33
|
+
s = @server
|
|
34
|
+
response = ::Tag.search_by_href(s.href)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
s.tags = [] unless s.tags
|
|
38
|
+
response.each do |t|
|
|
39
|
+
s.tags += [ t['name'] ]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
@fields.split(",").each do |f|
|
|
43
|
+
if f =~ /^tag=([^,]+)/
|
|
44
|
+
tag_search_string = $1
|
|
45
|
+
s.tags.each do |tag|
|
|
46
|
+
output << tag if tag =~ /^#{tag_search_string}/
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
output << s.params[f]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
puts output.join(",")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Run a RightScript
|
|
3
|
+
#
|
|
4
|
+
module Chimp
|
|
5
|
+
class ExecRightScript < Executor
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
options = {:ignore_lock => true}.merge(@inputs)
|
|
9
|
+
|
|
10
|
+
if @timeout < 300
|
|
11
|
+
Log.error "timeout was less than 5 minutes! resetting to 5 minutes"
|
|
12
|
+
@timeout = 300
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
run_with_retry do
|
|
16
|
+
audit_entry = server.run_executable(@exec, options)
|
|
17
|
+
audit_entry.wait_for_state("completed", @timeout)
|
|
18
|
+
@results = audit_entry.summary
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def describe_work
|
|
23
|
+
return "ExecRightScript job_id=#{@job_id} script=\"#{@exec['right_script']['name']}\" server=\"#{@server['nickname']}\""
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def info
|
|
27
|
+
return @exec['right_script']['name']
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def target
|
|
31
|
+
return @server['nickname']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|