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,1181 @@
|
|
|
1
|
+
#
|
|
2
|
+
# The Chimp class encapsulates the command-line program logic
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
module Chimp
|
|
6
|
+
class Chimp
|
|
7
|
+
attr_accessor :concurrency, :delay, :retry_count, :progress, :prompt,
|
|
8
|
+
:quiet, :use_chimpd, :chimpd_host, :chimpd_port, :tags, :array_names,
|
|
9
|
+
:deployment_names, :script, :servers, :ssh, :report, :interactive, :action,
|
|
10
|
+
:limit_start, :limit_end, :dry_run, :group, :job_id, :verify
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# These class variables control verbosity
|
|
14
|
+
#
|
|
15
|
+
@@verbose = false
|
|
16
|
+
@@quiet = false
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Set up reasonable defaults
|
|
20
|
+
#
|
|
21
|
+
def initialize
|
|
22
|
+
#
|
|
23
|
+
# General configuration options
|
|
24
|
+
#
|
|
25
|
+
@progress = false
|
|
26
|
+
@prompt = true
|
|
27
|
+
@verify = true
|
|
28
|
+
@dry_run = false
|
|
29
|
+
@interactive = true
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Job control options
|
|
33
|
+
#
|
|
34
|
+
@concurrency = 1
|
|
35
|
+
@delay = 0
|
|
36
|
+
@retry_count = 0
|
|
37
|
+
@timeout = 900
|
|
38
|
+
|
|
39
|
+
@limit_start = 0
|
|
40
|
+
@limit_end = 0
|
|
41
|
+
|
|
42
|
+
#
|
|
43
|
+
# Action configuration
|
|
44
|
+
#
|
|
45
|
+
@action = :action_none
|
|
46
|
+
@group = :default
|
|
47
|
+
@group_type = :parallel
|
|
48
|
+
@group_concurrency = 1
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Options for selecting objects to work on
|
|
52
|
+
#
|
|
53
|
+
@current = true
|
|
54
|
+
@match_all = true
|
|
55
|
+
@servers = []
|
|
56
|
+
@arrays = []
|
|
57
|
+
@tags = []
|
|
58
|
+
@array_names = []
|
|
59
|
+
@deployment_names = []
|
|
60
|
+
@template = nil
|
|
61
|
+
@script = nil
|
|
62
|
+
@ssh = nil
|
|
63
|
+
@ssh_user = "rightscale"
|
|
64
|
+
@report = nil
|
|
65
|
+
@inputs = {}
|
|
66
|
+
@set_tags = []
|
|
67
|
+
@ignore_errors = false
|
|
68
|
+
|
|
69
|
+
@break_array_into_instances = false
|
|
70
|
+
@dont_check_templates_for_script = false
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# chimpd configuration
|
|
74
|
+
#
|
|
75
|
+
@use_chimpd = false
|
|
76
|
+
@chimpd_host = 'localhost'
|
|
77
|
+
@chimpd_port = 9055
|
|
78
|
+
@chimpd_wait_until_done = false
|
|
79
|
+
|
|
80
|
+
RestClient.log = nil
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
#
|
|
84
|
+
# Entry point for the chimp command line application
|
|
85
|
+
#
|
|
86
|
+
def run
|
|
87
|
+
queue = ChimpQueue.instance
|
|
88
|
+
|
|
89
|
+
parse_command_line if @interactive
|
|
90
|
+
check_option_validity if @interactive
|
|
91
|
+
disable_logging unless @@verbose
|
|
92
|
+
|
|
93
|
+
puts "chimp #{VERSION} executing..." if (@interactive and not @use_chimpd) and not @@quiet
|
|
94
|
+
|
|
95
|
+
#
|
|
96
|
+
# Wait for chimpd to complete tasks
|
|
97
|
+
#
|
|
98
|
+
if @chimpd_wait_until_done
|
|
99
|
+
chimpd_wait_until_done
|
|
100
|
+
exit
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#
|
|
104
|
+
# Send the command to chimpd for execution
|
|
105
|
+
#
|
|
106
|
+
if @use_chimpd
|
|
107
|
+
ChimpDaemonClient.submit(@chimpd_host, @chimpd_port, self)
|
|
108
|
+
exit
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# If we're processing the command ourselves, then go
|
|
113
|
+
# ahead and start making API calls to select the objects
|
|
114
|
+
# to operate upon
|
|
115
|
+
#
|
|
116
|
+
get_array_info
|
|
117
|
+
get_server_info
|
|
118
|
+
get_template_info
|
|
119
|
+
get_executable_info
|
|
120
|
+
|
|
121
|
+
#
|
|
122
|
+
# Optionally display the list of objects to operate on
|
|
123
|
+
# and prompt the user
|
|
124
|
+
#
|
|
125
|
+
if @prompt and @interactive
|
|
126
|
+
list_of_objects = make_human_readable_list_of_objects
|
|
127
|
+
confirm = (list_of_objects.size > 0 and @action != :action_none) or @action == :action_none
|
|
128
|
+
|
|
129
|
+
verify("Your command will be executed on the following:", list_of_objects, confirm)
|
|
130
|
+
|
|
131
|
+
if @servers.length >= 2 and @server_template and @executable and not @dont_check_templates_for_script
|
|
132
|
+
warn_if_rightscript_not_in_all_servers @servers, @server_template, @executable
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
#
|
|
137
|
+
# Load the queue with work
|
|
138
|
+
#
|
|
139
|
+
jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
|
|
140
|
+
add_to_queue(jobs)
|
|
141
|
+
|
|
142
|
+
#
|
|
143
|
+
# Exit early if there is nothing to do
|
|
144
|
+
#
|
|
145
|
+
if @action == :action_none or queue.group[@group].size == 0
|
|
146
|
+
puts "No actions to perform." unless @@quiet
|
|
147
|
+
else
|
|
148
|
+
do_work
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
#
|
|
153
|
+
# Process a non-interactive chimp object command
|
|
154
|
+
# Used by chimpd
|
|
155
|
+
#
|
|
156
|
+
def process
|
|
157
|
+
get_array_info
|
|
158
|
+
get_server_info
|
|
159
|
+
get_template_info
|
|
160
|
+
get_executable_info
|
|
161
|
+
jobs = generate_jobs(@servers, @arrays, @server_template, @executable)
|
|
162
|
+
return(jobs)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
#
|
|
166
|
+
# Get the ServerTemplate info from the API
|
|
167
|
+
#
|
|
168
|
+
def get_template_info
|
|
169
|
+
if not (@servers.empty? and @array_names.empty?)
|
|
170
|
+
@server_template = detect_server_template(@template, @script, @servers, @array_names)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
#
|
|
175
|
+
# Get the Executable (RightScript) info from the API
|
|
176
|
+
#
|
|
177
|
+
def get_executable_info
|
|
178
|
+
if not (@servers.empty? and @array_names.empty?)
|
|
179
|
+
@executable = detect_right_script(@server_template, @script)
|
|
180
|
+
puts "Using SSH command: \"#{@ssh}\"" if @action == :action_ssh
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
#
|
|
185
|
+
# Parse command line options
|
|
186
|
+
#
|
|
187
|
+
def parse_command_line
|
|
188
|
+
begin
|
|
189
|
+
opts = GetoptLong.new(
|
|
190
|
+
[ '--tag', '-t', GetoptLong::REQUIRED_ARGUMENT ],
|
|
191
|
+
[ '--tag-use-and', '-a', GetoptLong::NO_ARGUMENT ],
|
|
192
|
+
[ '--tag-use-or', '-o', GetoptLong::NO_ARGUMENT ],
|
|
193
|
+
[ '--array', '-r', GetoptLong::REQUIRED_ARGUMENT ],
|
|
194
|
+
[ '--deployment', '-e', GetoptLong::REQUIRED_ARGUMENT ],
|
|
195
|
+
[ '--script', '-s', GetoptLong::OPTIONAL_ARGUMENT ],
|
|
196
|
+
[ '--ssh', '-x', GetoptLong::OPTIONAL_ARGUMENT ],
|
|
197
|
+
[ '--input', '-i', GetoptLong::REQUIRED_ARGUMENT ],
|
|
198
|
+
[ '--set-template', '-m', GetoptLong::REQUIRED_ARGUMENT ],
|
|
199
|
+
[ '--set-tag', '-w', GetoptLong::REQUIRED_ARGUMENT ],
|
|
200
|
+
[ '--report', '-b', GetoptLong::REQUIRED_ARGUMENT ],
|
|
201
|
+
[ '--progress', '-p', GetoptLong::NO_ARGUMENT ],
|
|
202
|
+
[ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
|
|
203
|
+
[ '--quiet', '-q', GetoptLong::NO_ARGUMENT ],
|
|
204
|
+
[ '--noprompt', '-z', GetoptLong::NO_ARGUMENT ],
|
|
205
|
+
[ '--concurrency', '-c', GetoptLong::REQUIRED_ARGUMENT ],
|
|
206
|
+
[ '--delay', '-d', GetoptLong::REQUIRED_ARGUMENT ],
|
|
207
|
+
[ '--retry', '-y', GetoptLong::REQUIRED_ARGUMENT ],
|
|
208
|
+
[ '--dry-run', '-n', GetoptLong::NO_ARGUMENT ],
|
|
209
|
+
[ '--limit', '-l', GetoptLong::REQUIRED_ARGUMENT ],
|
|
210
|
+
[ '--version', '-1', GetoptLong::NO_ARGUMENT ],
|
|
211
|
+
[ '--chimpd', '-f', GetoptLong::NO_ARGUMENT ],
|
|
212
|
+
[ '--chimpd-wait-until-done', '-j', GetoptLong::NO_ARGUMENT ],
|
|
213
|
+
[ '--dont-check-templates', '-0', GetoptLong::NO_ARGUMENT ],
|
|
214
|
+
[ '--ignore-errors', '-9', GetoptLong::NO_ARGUMENT ],
|
|
215
|
+
[ '--ssh-user', '-u', GetoptLong::REQUIRED_ARGUMENT ],
|
|
216
|
+
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
|
217
|
+
[ '--group', '-g', GetoptLong::REQUIRED_ARGUMENT ],
|
|
218
|
+
[ '--group-type', '-2', GetoptLong::REQUIRED_ARGUMENT ],
|
|
219
|
+
[ '--group-concurrency', '-3', GetoptLong::REQUIRED_ARGUMENT ],
|
|
220
|
+
[ '--timing-log', '-4', GetoptLong::REQUIRED_ARGUMENT ],
|
|
221
|
+
[ '--timeout', '-5', GetoptLong::REQUIRED_ARGUMENT ],
|
|
222
|
+
[ '--noverify', '-6', GetoptLong::NO_ARGUMENT ]
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
opts.each do |opt, arg|
|
|
226
|
+
case opt
|
|
227
|
+
when '--help', '-h'
|
|
228
|
+
help
|
|
229
|
+
exit 0
|
|
230
|
+
when '--tag', '-t'
|
|
231
|
+
@tags << arg
|
|
232
|
+
when '--tag-use-and', '-a'
|
|
233
|
+
@match_all = true
|
|
234
|
+
when '--tag-use-or', '-o'
|
|
235
|
+
@match_all = false
|
|
236
|
+
when '--array', '-a'
|
|
237
|
+
@array_names << arg
|
|
238
|
+
when '--deployment', '-e'
|
|
239
|
+
@deployment_names << arg
|
|
240
|
+
when '--template', '-m'
|
|
241
|
+
@template = arg
|
|
242
|
+
when '--script', '-s'
|
|
243
|
+
set_action(:action_rightscript)
|
|
244
|
+
if arg == ""
|
|
245
|
+
# Empty but not nil means show list of operational scripts to choose from
|
|
246
|
+
@script = ""
|
|
247
|
+
else
|
|
248
|
+
@script = arg
|
|
249
|
+
end
|
|
250
|
+
when '--ssh', '-x'
|
|
251
|
+
set_action(:action_ssh)
|
|
252
|
+
@break_array_into_instances = true
|
|
253
|
+
if arg == ""
|
|
254
|
+
print "Enter SSH command line to execute: "
|
|
255
|
+
@ssh = gets.chomp
|
|
256
|
+
else
|
|
257
|
+
@ssh = arg
|
|
258
|
+
end
|
|
259
|
+
when '--ssh-user', '-u'
|
|
260
|
+
@ssh_user = arg
|
|
261
|
+
when '--input', '-i'
|
|
262
|
+
arg =~ /(.+)=(.+)/
|
|
263
|
+
@inputs[$1]=$2
|
|
264
|
+
when '--set-template', '-m'
|
|
265
|
+
set_action(:action_set)
|
|
266
|
+
@template = arg
|
|
267
|
+
when '--set-tag', '-w'
|
|
268
|
+
set_action(:action_set)
|
|
269
|
+
@set_tags << arg
|
|
270
|
+
when '--report', '-b'
|
|
271
|
+
set_action(:action_report)
|
|
272
|
+
@report = arg
|
|
273
|
+
@@verbose = false
|
|
274
|
+
@@quiet = true
|
|
275
|
+
@break_array_into_instances = true
|
|
276
|
+
@concurrency = 5 if @concurrency == 1
|
|
277
|
+
when '--progress', '-p'
|
|
278
|
+
@progress = @progress ? false : true
|
|
279
|
+
when '--noprompt', '-z'
|
|
280
|
+
@prompt = false
|
|
281
|
+
when '--concurrency', '-c'
|
|
282
|
+
@concurrency = arg.to_i
|
|
283
|
+
when '--delay', '-d'
|
|
284
|
+
@delay = arg.to_i
|
|
285
|
+
when '--retry', '-y'
|
|
286
|
+
@retry_count = arg.to_i
|
|
287
|
+
when '--limit', '-l'
|
|
288
|
+
@limit_start, @limit_end = arg.split(',')
|
|
289
|
+
when '--verbose', '-v'
|
|
290
|
+
@@verbose = true
|
|
291
|
+
when '--quiet', '-q'
|
|
292
|
+
@@quiet = true
|
|
293
|
+
when '--dont-check-templates', '-0'
|
|
294
|
+
@dont_check_templates_for_script = true
|
|
295
|
+
when '--version'
|
|
296
|
+
puts VERSION
|
|
297
|
+
exit 0
|
|
298
|
+
when '--chimpd'
|
|
299
|
+
@use_chimpd = true
|
|
300
|
+
when '--chimpd-wait-until-done'
|
|
301
|
+
@use_chimpd = true
|
|
302
|
+
@chimpd_wait_until_done = true
|
|
303
|
+
when '--dry-run', '-n'
|
|
304
|
+
@dry_run = true
|
|
305
|
+
when '--ignore-errors', '-9'
|
|
306
|
+
@ignore_errors = true
|
|
307
|
+
when '--group', '-g'
|
|
308
|
+
@group = arg.to_sym
|
|
309
|
+
when '--group-type'
|
|
310
|
+
@group_type = arg.to_sym
|
|
311
|
+
when '--group-concurrency'
|
|
312
|
+
@group_concurrency = arg.to_i
|
|
313
|
+
when '--timing-log'
|
|
314
|
+
@timing_log = arg
|
|
315
|
+
when '--timeout'
|
|
316
|
+
@timeout = arg
|
|
317
|
+
when '--noverify'
|
|
318
|
+
@verify = false
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
rescue GetoptLong::InvalidOption => ex
|
|
322
|
+
help
|
|
323
|
+
exit 1
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
#
|
|
327
|
+
# Before we're totally done parsing command line options,
|
|
328
|
+
# let's make sure that a few things make sense
|
|
329
|
+
#
|
|
330
|
+
if @group_concurrency > @concurrency
|
|
331
|
+
@concurrency = @group_concurrency
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
#
|
|
337
|
+
# Check for any invalid combinations of command line options
|
|
338
|
+
#
|
|
339
|
+
def check_option_validity
|
|
340
|
+
if @tags.empty? and @array_names.empty? and @deployment_names.empty? and not @chimpd_wait_until_done
|
|
341
|
+
puts "ERROR: Please select the objects to operate upon."
|
|
342
|
+
help
|
|
343
|
+
exit 1
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
if not @array_names.empty? and ( not @tags.empty? or not @deployment_names.empty? )
|
|
347
|
+
puts "ERROR: You cannot mix ServerArray queries with other types of queries."
|
|
348
|
+
help
|
|
349
|
+
exit 1
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
#
|
|
354
|
+
# Go through each of the various ways to specify servers via
|
|
355
|
+
# the command line (tags, deployments, etc.) and get all the info
|
|
356
|
+
# needed from the RightScale API.
|
|
357
|
+
#
|
|
358
|
+
def get_server_info
|
|
359
|
+
@servers += get_servers_by_tag(@tags)
|
|
360
|
+
@servers += get_servers_by_deployment(@deployment_names)
|
|
361
|
+
@servers = filter_out_non_operational_servers(@servers)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
#
|
|
365
|
+
# Load up @array with server arrays to operate on
|
|
366
|
+
#
|
|
367
|
+
def get_array_info
|
|
368
|
+
return if @array_names.empty?
|
|
369
|
+
|
|
370
|
+
#
|
|
371
|
+
# Some operations (e.g. ExecSSH) require individual server information.
|
|
372
|
+
# Check for @break_array_into_instances and break up the ServerArray
|
|
373
|
+
# into Servers as necessary.
|
|
374
|
+
#
|
|
375
|
+
if @break_array_into_instances
|
|
376
|
+
Log.debug "Breaking array into instances..."
|
|
377
|
+
@servers += get_servers_by_array(@array_names)
|
|
378
|
+
@array_names = []
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
@array_names.each do |array_name|
|
|
382
|
+
Log.debug "Querying API for ServerArray \'#{array_name}\'..."
|
|
383
|
+
a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
|
|
384
|
+
if not a.nil?
|
|
385
|
+
@arrays << a
|
|
386
|
+
else
|
|
387
|
+
if @ignore_errors
|
|
388
|
+
Log.warn "cannot find ServerArray #{array_name}"
|
|
389
|
+
else
|
|
390
|
+
raise "cannot find ServerArray #{array_name}"
|
|
391
|
+
end
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
#
|
|
397
|
+
# Get servers to operate on via a tag query
|
|
398
|
+
#
|
|
399
|
+
# Returns: array of RestConnection::Server objects
|
|
400
|
+
#
|
|
401
|
+
def get_servers_by_tag(tags)
|
|
402
|
+
return([]) unless tags.size > 0
|
|
403
|
+
servers = ::Tag.search("ec2_instance", tags, :match_all => @match_all)
|
|
404
|
+
|
|
405
|
+
if tags.size > 0 and servers.nil? or servers.empty?
|
|
406
|
+
if @ignore_errors
|
|
407
|
+
Log.warn "Tag query returned no results: #{tags.join(" ")}"
|
|
408
|
+
else
|
|
409
|
+
raise "Tag query returned no results: #{tags.join(" ")}"
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
return(servers)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
#
|
|
417
|
+
# Parse deployment names and get Server objects
|
|
418
|
+
#
|
|
419
|
+
# Returns: array of RestConnection::Server objects
|
|
420
|
+
#
|
|
421
|
+
def get_servers_by_deployment(names)
|
|
422
|
+
servers = []
|
|
423
|
+
|
|
424
|
+
if names.size > 0
|
|
425
|
+
names.each do |deployment|
|
|
426
|
+
d = ::Deployment.find_by_nickname(deployment).first
|
|
427
|
+
|
|
428
|
+
if d == nil
|
|
429
|
+
if @ignore_errors
|
|
430
|
+
Log.warn "cannot find deployment #{deployment}"
|
|
431
|
+
else
|
|
432
|
+
raise "cannot find deployment #{deployment}"
|
|
433
|
+
end
|
|
434
|
+
else
|
|
435
|
+
d.servers_no_reload.each do |s|
|
|
436
|
+
servers << s
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
return(servers)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
#
|
|
446
|
+
# Parse array names
|
|
447
|
+
#
|
|
448
|
+
# Returns: array of RestConnection::Server objects
|
|
449
|
+
#
|
|
450
|
+
def get_servers_by_array(names)
|
|
451
|
+
array_servers = []
|
|
452
|
+
if names.size > 0
|
|
453
|
+
names.each do |array_name|
|
|
454
|
+
all_arrays = ::Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }
|
|
455
|
+
|
|
456
|
+
if all_arrays != nil and all_arrays.first != nil
|
|
457
|
+
all_arrays.first.instances.each do |s|
|
|
458
|
+
array_servers << s
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
return(array_servers)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
#
|
|
468
|
+
# ServerTemplate auto-detection
|
|
469
|
+
#
|
|
470
|
+
# Returns: RestConnection::ServerTemplate
|
|
471
|
+
#
|
|
472
|
+
def detect_server_template(template, script, servers, array_names_to_detect)
|
|
473
|
+
st = nil
|
|
474
|
+
|
|
475
|
+
#
|
|
476
|
+
# If we have a script name but no template, check
|
|
477
|
+
# each server for the script until we locate it.
|
|
478
|
+
#
|
|
479
|
+
if script and template == nil
|
|
480
|
+
Log.debug "getting template URI..."
|
|
481
|
+
|
|
482
|
+
if not servers.empty?
|
|
483
|
+
for i in (0..servers.size - 1)
|
|
484
|
+
|
|
485
|
+
template = servers[i]['server_template_href'] if not servers[i].empty?
|
|
486
|
+
break if template
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
elsif not array_names_to_detect.empty?
|
|
490
|
+
array_names_to_detect.each do |array_name|
|
|
491
|
+
a = Ec2ServerArray.find_by(:nickname) { |n| n =~ /^#{array_name}/i }.first
|
|
492
|
+
next unless a
|
|
493
|
+
template = a['server_template_href']
|
|
494
|
+
break if template
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
raise "Unable to locate ServerTemplate!" unless template
|
|
499
|
+
Log.debug "Template: #{template}"
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
#
|
|
503
|
+
# Now look up the ServerTemplate via the RightScale API
|
|
504
|
+
#
|
|
505
|
+
if template
|
|
506
|
+
Log.debug "Looking up template..."
|
|
507
|
+
|
|
508
|
+
if template =~ /^http/
|
|
509
|
+
st = ::ServerTemplate.find(template)
|
|
510
|
+
else
|
|
511
|
+
st = ::ServerTemplate.find_by_nickname(template).first
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
if st == nil
|
|
515
|
+
raise "No matching ServerTemplate found!"
|
|
516
|
+
else
|
|
517
|
+
Log.debug "ServerTemplate: \"#{st['nickname']}\""
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
return(st)
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
#
|
|
525
|
+
# Look up the RightScript
|
|
526
|
+
#
|
|
527
|
+
# Returns: RestConnection::Executable
|
|
528
|
+
#
|
|
529
|
+
def detect_right_script(st, script)
|
|
530
|
+
executable = nil
|
|
531
|
+
|
|
532
|
+
if script == ""
|
|
533
|
+
if not @interactive
|
|
534
|
+
puts "Error: empty --script= option is supported only in interactive mode. Exiting."
|
|
535
|
+
exit 1
|
|
536
|
+
end
|
|
537
|
+
# Find operational scripts that exist in this server template
|
|
538
|
+
op_script_names = ['dummy name'] # Placeholder for #0 since we want to offer choices 1..n
|
|
539
|
+
op_script_hrefs = [ 'dummy href' ]
|
|
540
|
+
st.executables.each do |ex|
|
|
541
|
+
if ex.apply == "operational"
|
|
542
|
+
op_script_names.push( ex.name )
|
|
543
|
+
op_script_hrefs.push( ex.href )
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
if op_script_names.length <= 1
|
|
547
|
+
puts "Warning: No operational scripts found on the server(s). "
|
|
548
|
+
puts " (Search performed on server template '#{st.nickname}')"
|
|
549
|
+
else
|
|
550
|
+
puts "List of available operational scripts in the server template: ('#{st.nickname}')"
|
|
551
|
+
puts "------------------------------------------------------------"
|
|
552
|
+
for i in 1..op_script_names.length - 1
|
|
553
|
+
puts " %3d. #{op_script_names[i]}" % i
|
|
554
|
+
end
|
|
555
|
+
puts "------------------------------------------------------------"
|
|
556
|
+
while true
|
|
557
|
+
printf "Type the number of the script to run and press Enter (Ctrl-C to quit): "
|
|
558
|
+
op_script_id = Integer(gets.chomp) rescue -1
|
|
559
|
+
if op_script_id > 0 && op_script_id < op_script_names.length
|
|
560
|
+
puts "Script choice: #{op_script_id}. #{op_script_names[ op_script_id ]}"
|
|
561
|
+
break
|
|
562
|
+
else
|
|
563
|
+
puts "#{op_script_id < 0 ? 'Invalid input' : 'Input out of range'}."
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
# Provide the href as the input for the block that will do the lookup
|
|
567
|
+
script = op_script_hrefs[ op_script_id ]
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
if script
|
|
572
|
+
if script =~ /^http/ or script =~ /^\d+$/
|
|
573
|
+
if script =~ /^\d+$/
|
|
574
|
+
url_prefix = st.params['href'].match( /^.*\/acct\/\d+/)[0] # extract the 'https://my.rightscale.com/api/acct/<account_id>' part from the template's href
|
|
575
|
+
script = url_prefix + "/right_scripts/#{script}"
|
|
576
|
+
end
|
|
577
|
+
script_URI = script
|
|
578
|
+
Log.debug "Looking for script href \"#{script_URI}\""
|
|
579
|
+
puts
|
|
580
|
+
# First look up the script URI in the template.
|
|
581
|
+
# It *will* be found if we came here from the 'if script = ""' block
|
|
582
|
+
script = st.executables.detect { |ex| ex.href == script }
|
|
583
|
+
if not script
|
|
584
|
+
script_obj = ::RightScript.find(script_URI)
|
|
585
|
+
script_data = {}
|
|
586
|
+
script_data[ 'name' ] = script_obj.params['name']
|
|
587
|
+
script = ::RightScript.new({ :href => script_URI, :right_script => script_data })
|
|
588
|
+
end
|
|
589
|
+
else
|
|
590
|
+
Log.debug "looking for script \"#{script}\""
|
|
591
|
+
script = st.executables.detect { |ex| ex.name =~ /#{script}/ }
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
if script != nil and script['right_script'] != nil
|
|
595
|
+
puts "RightScript: \"#{script['right_script']['name']}\"" if @interactive
|
|
596
|
+
else
|
|
597
|
+
puts "No matching RightScript found!"
|
|
598
|
+
raise "No matching RightScript found!"
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
executable = script
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
return(executable)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
#
|
|
608
|
+
# Load up the queue with work
|
|
609
|
+
#
|
|
610
|
+
# FIXME this needs to be refactored
|
|
611
|
+
#
|
|
612
|
+
def generate_jobs(queue_servers, queue_arrays, queue_template, queue_executable)
|
|
613
|
+
counter = 0
|
|
614
|
+
tasks = []
|
|
615
|
+
Log.debug "Loading queue..."
|
|
616
|
+
|
|
617
|
+
#
|
|
618
|
+
# Configure group
|
|
619
|
+
#
|
|
620
|
+
if not ChimpQueue[@group]
|
|
621
|
+
ChimpQueue.instance.create_group(@group, @group_type, @group_concurrency)
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
#
|
|
625
|
+
# Process ServerArray selection
|
|
626
|
+
#
|
|
627
|
+
Log.debug("processing queue selection")
|
|
628
|
+
if not queue_arrays.empty?
|
|
629
|
+
queue_arrays.each do |array|
|
|
630
|
+
instances = filter_out_non_operational_servers(array.instances)
|
|
631
|
+
|
|
632
|
+
if not instances
|
|
633
|
+
Log.error("no instances in array!")
|
|
634
|
+
break
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
instances.each do |array_instance|
|
|
638
|
+
#
|
|
639
|
+
# Handle limiting options
|
|
640
|
+
#
|
|
641
|
+
counter += 1
|
|
642
|
+
next if @limit_start.to_i > 0 and counter < @limit_start.to_i
|
|
643
|
+
break if @limit_end.to_i > 0 and counter > @limit_end.to_i
|
|
644
|
+
a = ExecArray.new(:array => array, :server => array_instance, :exec => queue_executable, :template => queue_template, :verbose => @@verbose, :quiet => @@quiet)
|
|
645
|
+
a.dry_run = @dry_run
|
|
646
|
+
ChimpQueue.instance.push(@group, a)
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
#
|
|
652
|
+
# Process Server selection
|
|
653
|
+
#
|
|
654
|
+
Log.debug("Processing server selection")
|
|
655
|
+
|
|
656
|
+
queue_servers.sort! { |a,b| a['nickname'] <=> b['nickname'] }
|
|
657
|
+
queue_servers.each do |server|
|
|
658
|
+
|
|
659
|
+
#
|
|
660
|
+
# Handle limiting options
|
|
661
|
+
#
|
|
662
|
+
counter += 1
|
|
663
|
+
next if @limit_start.to_i > 0 and counter < @limit_start.to_i
|
|
664
|
+
break if @limit_end.to_i > 0 and counter > @limit_end.to_i
|
|
665
|
+
|
|
666
|
+
#
|
|
667
|
+
# Construct the Server object
|
|
668
|
+
#
|
|
669
|
+
s = ::Server.new
|
|
670
|
+
s.href = server['href']
|
|
671
|
+
s.current_instance_href = server['current_instance_href']
|
|
672
|
+
s.name = server['nickname'] || server['name']
|
|
673
|
+
s.nickname = s.name
|
|
674
|
+
s.ip_address = server['ip-address'] || server['ip_address']
|
|
675
|
+
e = nil
|
|
676
|
+
|
|
677
|
+
if queue_executable
|
|
678
|
+
e = ExecRightScript.new(
|
|
679
|
+
:server => s,
|
|
680
|
+
:exec => queue_executable,
|
|
681
|
+
:inputs => @inputs,
|
|
682
|
+
:timeout => @timeout,
|
|
683
|
+
:verbose => @@verbose,
|
|
684
|
+
:quiet => @@quiet
|
|
685
|
+
)
|
|
686
|
+
elsif @ssh
|
|
687
|
+
e = ExecSSH.new(
|
|
688
|
+
:server => s,
|
|
689
|
+
:ssh_user => @ssh_user,
|
|
690
|
+
:exec => @ssh,
|
|
691
|
+
:verbose => @@verbose,
|
|
692
|
+
:quiet => @@quiet
|
|
693
|
+
)
|
|
694
|
+
elsif queue_template and not clone
|
|
695
|
+
e = ExecSetTemplate.new(
|
|
696
|
+
:server => s,
|
|
697
|
+
:template => queue_template,
|
|
698
|
+
:verbose => @@verbose,
|
|
699
|
+
:quiet => @@quiet
|
|
700
|
+
)
|
|
701
|
+
elsif @report
|
|
702
|
+
if s.href
|
|
703
|
+
s.href = s.href.sub("/current","")
|
|
704
|
+
e = ExecReport.new(:server => s, :verbose => @@verbose, :quiet => @@quiet)
|
|
705
|
+
e.fields = @report
|
|
706
|
+
end
|
|
707
|
+
elsif @set_tags.size > 0
|
|
708
|
+
e = ExecSetTags.new(:server => s, :verbose => @@verbose, :quiet => @@quiet)
|
|
709
|
+
e.tags = set_tags
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
if e != nil
|
|
713
|
+
e.dry_run = @dry_run
|
|
714
|
+
e.quiet = @@quiet
|
|
715
|
+
tasks.push(e)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
return(tasks)
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
def add_to_queue(a)
|
|
724
|
+
a.each { |task| ChimpQueue.instance.push(@group, task) }
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
#
|
|
728
|
+
# Execute the user's command and provide for retrys etc.
|
|
729
|
+
#
|
|
730
|
+
def queue_runner(concurrency, delay, retry_count, progress)
|
|
731
|
+
queue = ChimpQueue.instance
|
|
732
|
+
queue.max_threads = concurrency
|
|
733
|
+
queue.delay = delay
|
|
734
|
+
queue.retry_count = retry_count
|
|
735
|
+
total_queue_size = queue.size
|
|
736
|
+
|
|
737
|
+
puts "Executing..." unless progress or not quiet
|
|
738
|
+
pbar = ProgressBar.new("Executing", 100) if progress
|
|
739
|
+
queue.start
|
|
740
|
+
|
|
741
|
+
queue.wait_until_done(@group) do
|
|
742
|
+
pbar.set(((total_queue_size.to_f - queue.size.to_f)/total_queue_size.to_f*100).to_i) if progress
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
pbar.finish if progress
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
#
|
|
749
|
+
# Set the action
|
|
750
|
+
#
|
|
751
|
+
def set_action(a)
|
|
752
|
+
raise ArgumentError.new "Cannot reset action" unless @action == :action_none
|
|
753
|
+
@action = a
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
#
|
|
757
|
+
# Allow user to verify results and retry if necessary
|
|
758
|
+
#
|
|
759
|
+
def verify_results(group = :default)
|
|
760
|
+
failed_workers, results_display = get_results(group)
|
|
761
|
+
|
|
762
|
+
#
|
|
763
|
+
# If no workers failed, then we're done.
|
|
764
|
+
#
|
|
765
|
+
return true if failed_workers.empty?
|
|
766
|
+
|
|
767
|
+
#
|
|
768
|
+
# Some workers failed; offer the user a chance to retry them
|
|
769
|
+
#
|
|
770
|
+
verify("The following objects failed:", results_display, false)
|
|
771
|
+
|
|
772
|
+
while true
|
|
773
|
+
puts "(R)etry failed jobs"
|
|
774
|
+
puts "(A)bort chimp run"
|
|
775
|
+
puts "(I)gnore errors and continue"
|
|
776
|
+
command = gets()
|
|
777
|
+
|
|
778
|
+
if command =~ /^a/i
|
|
779
|
+
puts "Aborting!"
|
|
780
|
+
exit 1
|
|
781
|
+
elsif command =~ /^i/i
|
|
782
|
+
puts "Ignoring errors and continuing"
|
|
783
|
+
exit 0
|
|
784
|
+
elsif command =~ /^r/i
|
|
785
|
+
puts "Retrying..."
|
|
786
|
+
ChimpQueue.instance.group[group].requeue_failed_jobs!
|
|
787
|
+
return false
|
|
788
|
+
end
|
|
789
|
+
end
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
#
|
|
793
|
+
# Get the results from the QueueRunner and format them
|
|
794
|
+
# in a way that's easy to display to the user
|
|
795
|
+
#
|
|
796
|
+
def get_results(group_name)
|
|
797
|
+
queue = ChimpQueue.instance
|
|
798
|
+
Log.debug("getting results for group #{group_name}")
|
|
799
|
+
results = queue.group[@group].results()
|
|
800
|
+
failed_workers = []
|
|
801
|
+
results_display = []
|
|
802
|
+
|
|
803
|
+
results.each do |result|
|
|
804
|
+
next if result == nil
|
|
805
|
+
|
|
806
|
+
if result[:status] == :error
|
|
807
|
+
name = result[:host] || "unknown"
|
|
808
|
+
message = result[:error].to_s || "unknown"
|
|
809
|
+
message.sub!("\n", "")
|
|
810
|
+
failed_workers << result[:worker]
|
|
811
|
+
results_display << "#{name.ljust(40)} #{message}"
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
|
|
815
|
+
return [failed_workers, results_display]
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
def print_timings
|
|
819
|
+
ChimpQueue.instance.group[@group].results.each do |task|
|
|
820
|
+
puts "Host: #{task[:host]} Type: #{task[:name]} Time: #{task[:total]} seconds"
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
def get_failures
|
|
825
|
+
return get_results(@group)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
#
|
|
829
|
+
# Filter out non-operational servers
|
|
830
|
+
# Then add operational servers to the list of objects to display
|
|
831
|
+
#
|
|
832
|
+
def filter_out_non_operational_servers(servers)
|
|
833
|
+
Log.debug "Filtering out non-operational servers..."
|
|
834
|
+
servers.reject! { |s| s == nil || s['state'] != "operational" }
|
|
835
|
+
return(servers)
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
#
|
|
839
|
+
# Do work: either by submitting to chimpd
|
|
840
|
+
# or running it ourselves.
|
|
841
|
+
#
|
|
842
|
+
def do_work
|
|
843
|
+
done = false
|
|
844
|
+
|
|
845
|
+
while not done
|
|
846
|
+
queue_runner(@concurrency, @delay, @retry_count, @progress)
|
|
847
|
+
|
|
848
|
+
if @interactive and @verify
|
|
849
|
+
done = verify_results(@group)
|
|
850
|
+
else
|
|
851
|
+
done = true
|
|
852
|
+
end
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
if not @verify
|
|
856
|
+
failed_workers, results_display = get_results(group)
|
|
857
|
+
exit 1 if failed_workers.size > 0
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
puts "chimp run complete"
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
#
|
|
864
|
+
# Completely process a non-interactive chimp object command
|
|
865
|
+
#
|
|
866
|
+
def process
|
|
867
|
+
get_array_info
|
|
868
|
+
get_server_info
|
|
869
|
+
get_template_info
|
|
870
|
+
get_executable_info
|
|
871
|
+
return generate_jobs(@servers, @arrays, @server_template, @executable)
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
#
|
|
875
|
+
# Always returns 0. Used for chimpd compatibility.
|
|
876
|
+
#
|
|
877
|
+
def job_id
|
|
878
|
+
return 0
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
#
|
|
882
|
+
# Connect to chimpd and wait for the work queue to empty, and
|
|
883
|
+
# prompt the user if there are any errors.
|
|
884
|
+
#
|
|
885
|
+
def chimpd_wait_until_done
|
|
886
|
+
local_queue = ChimpQueue.instance
|
|
887
|
+
|
|
888
|
+
begin
|
|
889
|
+
while true
|
|
890
|
+
local_queue = ChimpQueue.instance
|
|
891
|
+
|
|
892
|
+
#
|
|
893
|
+
# load up remote chimpd jobs into the local queue
|
|
894
|
+
# this makes all the standard queue control methods available to us
|
|
895
|
+
#
|
|
896
|
+
retry_count = 1
|
|
897
|
+
while true
|
|
898
|
+
local_queue.reset!
|
|
899
|
+
|
|
900
|
+
begin
|
|
901
|
+
puts "Waiting for chimpd jobs to complete for group #{@group}..."
|
|
902
|
+
all = ChimpDaemonClient.retrieve_group_info(@chimpd_host, @chimpd_port, @group, :all)
|
|
903
|
+
rescue RestClient::ResourceNotFound
|
|
904
|
+
if retry_count > 0
|
|
905
|
+
retry_count -= 1
|
|
906
|
+
sleep 5
|
|
907
|
+
retry
|
|
908
|
+
end
|
|
909
|
+
|
|
910
|
+
if @ignore_errors
|
|
911
|
+
exit 0
|
|
912
|
+
else
|
|
913
|
+
$stderr.puts "ERROR: Group \"#{group}\" not found!"
|
|
914
|
+
exit 1
|
|
915
|
+
end
|
|
916
|
+
end
|
|
917
|
+
|
|
918
|
+
ChimpQueue.instance.create_group(@group)
|
|
919
|
+
ChimpQueue[@group].set_jobs(all)
|
|
920
|
+
|
|
921
|
+
break if ChimpQueue[@group].done?
|
|
922
|
+
|
|
923
|
+
$stdout.print "."
|
|
924
|
+
$stdout.flush
|
|
925
|
+
sleep 5
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
#
|
|
929
|
+
# If verify_results returns true, then ask chimpd to requeue all failed jobs.
|
|
930
|
+
#
|
|
931
|
+
if verify_results(@group)
|
|
932
|
+
break
|
|
933
|
+
else
|
|
934
|
+
ChimpDaemonClient.retry_group(@chimpd_host, @chimpd_port, @group)
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
ensure
|
|
938
|
+
#$stdout.print " done\n"
|
|
939
|
+
end
|
|
940
|
+
end
|
|
941
|
+
|
|
942
|
+
#
|
|
943
|
+
# Disable rest_connection logging
|
|
944
|
+
#
|
|
945
|
+
def disable_logging
|
|
946
|
+
ENV['REST_CONNECTION_LOG'] = "/dev/null"
|
|
947
|
+
ENV['RESTCLIENT_LOG'] = "/dev/null"
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
#
|
|
951
|
+
# Configure the Log object
|
|
952
|
+
#
|
|
953
|
+
def self.set_verbose(v=true, q=false)
|
|
954
|
+
@@verbose = v
|
|
955
|
+
@@quiet = q
|
|
956
|
+
|
|
957
|
+
STDOUT.sync = true
|
|
958
|
+
STDERR.sync = true
|
|
959
|
+
|
|
960
|
+
if @@verbose == true
|
|
961
|
+
Log.threshold = Logger::DEBUG
|
|
962
|
+
elsif @@quiet == true
|
|
963
|
+
Log.threshold = Logger::WARN
|
|
964
|
+
else
|
|
965
|
+
Log.threshold = Logger::INFO
|
|
966
|
+
end
|
|
967
|
+
end
|
|
968
|
+
|
|
969
|
+
def self.verbose?
|
|
970
|
+
return @@verbose
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
#
|
|
974
|
+
# Always returns 0. Used for chimpd compatibility.
|
|
975
|
+
#
|
|
976
|
+
def job_id
|
|
977
|
+
return 0
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
####################################################
|
|
981
|
+
private
|
|
982
|
+
####################################################
|
|
983
|
+
|
|
984
|
+
#
|
|
985
|
+
# Allow the user to verify the list of servers that an
|
|
986
|
+
# operation will be run against.
|
|
987
|
+
#
|
|
988
|
+
def verify(message, items, confirm=true)
|
|
989
|
+
puts message
|
|
990
|
+
puts "=================================================="
|
|
991
|
+
|
|
992
|
+
i = 0
|
|
993
|
+
items.sort.each do |item|
|
|
994
|
+
i += 1
|
|
995
|
+
puts " %03d. #{item}" % i
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
puts "=================================================="
|
|
999
|
+
|
|
1000
|
+
if confirm
|
|
1001
|
+
puts "Press enter to confirm or ^C to exit"
|
|
1002
|
+
gets
|
|
1003
|
+
end
|
|
1004
|
+
end
|
|
1005
|
+
|
|
1006
|
+
#
|
|
1007
|
+
# Verify that the given rightscript_executable (the object corresponding to the script)
|
|
1008
|
+
# that is associated with the server_template exists in all servers
|
|
1009
|
+
# (No need to check server arrays, they must all have the same template.)
|
|
1010
|
+
#
|
|
1011
|
+
# Returns: none. Prints a warning if any server does not have the script in its template.
|
|
1012
|
+
#
|
|
1013
|
+
def warn_if_rightscript_not_in_all_servers(servers, server_template, rightscript_executable)
|
|
1014
|
+
|
|
1015
|
+
return if servers.length < 2 or not server_template or not rightscript_executable
|
|
1016
|
+
|
|
1017
|
+
main_server_template = server_template
|
|
1018
|
+
main_server_template_name = main_server_template.params['nickname']
|
|
1019
|
+
main_server_template_href = main_server_template.params['href']
|
|
1020
|
+
|
|
1021
|
+
# Find which server has the specified template (the "main" template)
|
|
1022
|
+
server_that_has_main_template = nil
|
|
1023
|
+
for i in (0..servers.length - 1)
|
|
1024
|
+
if servers[i] and servers[i]['server_template_href'] == main_server_template_href
|
|
1025
|
+
server_that_has_main_template = servers[i]
|
|
1026
|
+
break
|
|
1027
|
+
end
|
|
1028
|
+
end
|
|
1029
|
+
if not server_that_has_main_template
|
|
1030
|
+
puts "internal error validating rightscript presence in all servers"
|
|
1031
|
+
return
|
|
1032
|
+
end
|
|
1033
|
+
|
|
1034
|
+
some_servers_have_different_template = false
|
|
1035
|
+
num_servers_missing_rightscript = 0
|
|
1036
|
+
|
|
1037
|
+
for i in (0..servers.length - 1)
|
|
1038
|
+
next if servers[i].empty?
|
|
1039
|
+
|
|
1040
|
+
this_server_template_href = servers[i]['server_template_href']
|
|
1041
|
+
|
|
1042
|
+
# If the server's template has the same href, this server is good
|
|
1043
|
+
next if this_server_template_href == main_server_template_href
|
|
1044
|
+
|
|
1045
|
+
if not some_servers_have_different_template
|
|
1046
|
+
some_servers_have_different_template = true
|
|
1047
|
+
if not @@quiet
|
|
1048
|
+
puts "Note: servers below have different server templates:"
|
|
1049
|
+
puts " - server '#{server_that_has_main_template['nickname']}: "
|
|
1050
|
+
if @@verbose
|
|
1051
|
+
puts " template name: '#{main_server_template_name}'"
|
|
1052
|
+
puts " href: '#{main_server_template_href}'"
|
|
1053
|
+
end
|
|
1054
|
+
end
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
this_server_template = ::ServerTemplate.find(this_server_template_href)
|
|
1058
|
+
next if this_server_template == nil
|
|
1059
|
+
if not @@quiet
|
|
1060
|
+
puts " - server '#{servers[i]['nickname']}: "
|
|
1061
|
+
if @@verbose
|
|
1062
|
+
puts " template name: '#{this_server_template.params['nickname']}'"
|
|
1063
|
+
puts " href: '#{this_server_template.params['href']}'"
|
|
1064
|
+
end
|
|
1065
|
+
end
|
|
1066
|
+
|
|
1067
|
+
# Now check if the offending template has the rightscript in question
|
|
1068
|
+
has_script = false
|
|
1069
|
+
this_server_template.executables.each do |cur_script|
|
|
1070
|
+
if rightscript_executable['right_script']['href'] == cur_script['right_script']['href']
|
|
1071
|
+
has_script = true
|
|
1072
|
+
break
|
|
1073
|
+
end
|
|
1074
|
+
end
|
|
1075
|
+
if not has_script
|
|
1076
|
+
if not @@quiet
|
|
1077
|
+
puts " >> WARNING: The above server's template does not include the execution rightscript!"
|
|
1078
|
+
end
|
|
1079
|
+
num_servers_missing_rightscript += 1
|
|
1080
|
+
if num_servers_missing_rightscript == 1
|
|
1081
|
+
if @@verbose
|
|
1082
|
+
puts " script name: \'#{rightscript_executable['right_script']['name']}\', href: \'#{rightscript_executable['right_script']['href']}\'"
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
end
|
|
1086
|
+
end
|
|
1087
|
+
if some_servers_have_different_template
|
|
1088
|
+
if num_servers_missing_rightscript == 0
|
|
1089
|
+
puts "Script OK. The servers have different templates, but they all contain the script, \'#{rightscript_executable['right_script']['name']}\'"
|
|
1090
|
+
else
|
|
1091
|
+
puts "WARNING: total of #{num_servers_missing_rightscript} servers listed do not have the rightscript in their template."
|
|
1092
|
+
end
|
|
1093
|
+
else
|
|
1094
|
+
if not @@quiet
|
|
1095
|
+
puts "Script OK. All the servers share the same template and the script is included in it."
|
|
1096
|
+
end
|
|
1097
|
+
end
|
|
1098
|
+
puts
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
#
|
|
1102
|
+
# Generate a human readable list of objects
|
|
1103
|
+
#
|
|
1104
|
+
def make_human_readable_list_of_objects
|
|
1105
|
+
list_of_objects = []
|
|
1106
|
+
|
|
1107
|
+
if @servers
|
|
1108
|
+
list_of_objects += @servers.map { |s| s['nickname'] }
|
|
1109
|
+
end
|
|
1110
|
+
|
|
1111
|
+
if @arrays
|
|
1112
|
+
@arrays.each do |a|
|
|
1113
|
+
i = filter_out_non_operational_servers(a.instances)
|
|
1114
|
+
list_of_objects += i.map { |j| j['nickname'] }
|
|
1115
|
+
end
|
|
1116
|
+
end
|
|
1117
|
+
return(list_of_objects)
|
|
1118
|
+
end
|
|
1119
|
+
|
|
1120
|
+
#
|
|
1121
|
+
# Print out help information
|
|
1122
|
+
#
|
|
1123
|
+
def help
|
|
1124
|
+
puts
|
|
1125
|
+
puts "chimp -- a RightScale Platform command-line tool"
|
|
1126
|
+
puts
|
|
1127
|
+
puts "To select servers using tags:"
|
|
1128
|
+
puts " --tag=<tag> example: --tag=service:dataservice=true"
|
|
1129
|
+
puts " --tag-use-and 'and' all tags when selecting servers (default)"
|
|
1130
|
+
puts " --tag-use-or 'or' all tags when selecting servers"
|
|
1131
|
+
puts
|
|
1132
|
+
puts "To select arrays or deployments:"
|
|
1133
|
+
puts " --array=<name> array to execute upon"
|
|
1134
|
+
puts " --deployment=<name> deployment to execute upon"
|
|
1135
|
+
puts
|
|
1136
|
+
puts "To perform an action, specify one of the following:"
|
|
1137
|
+
puts " --script=[<name>|<uri>|<id>] name/uri/id of RightScript to run, empty for opscripts list"
|
|
1138
|
+
puts " --report=<field-1>,<field-2>... produce a report (see below)"
|
|
1139
|
+
puts " --ssh=<command> command to execute via SSH"
|
|
1140
|
+
puts " --ssh-user=<username> username to use for SSH login (default: root)"
|
|
1141
|
+
puts
|
|
1142
|
+
puts "Action options:"
|
|
1143
|
+
puts " --input=\"<name>=<value>\" set input <name> for RightScript execution"
|
|
1144
|
+
puts
|
|
1145
|
+
puts "Execution options:"
|
|
1146
|
+
puts " --group=<name> specify an execution group"
|
|
1147
|
+
puts " --group-type=<serial|parallel> specify group execution type"
|
|
1148
|
+
puts " --group-concurrency=<n> specify group concurrency, e.g. for parallel groups"
|
|
1149
|
+
puts
|
|
1150
|
+
puts " --concurrency=<n> number of concurrent actions to perform. Default: 1"
|
|
1151
|
+
puts " --delay=<seconds> delay a number of seconds between operations"
|
|
1152
|
+
puts
|
|
1153
|
+
puts "General options:"
|
|
1154
|
+
puts " --dry-run only show what would be done"
|
|
1155
|
+
puts " --ignore-errors ignore errors when server selection fails"
|
|
1156
|
+
puts " --retry=<n> number of times to retry. Default: 0"
|
|
1157
|
+
puts " --timeout=<seconds> set the timeout to wait for a RightScript to complete"
|
|
1158
|
+
puts " --progress toggle progress indicator"
|
|
1159
|
+
puts " --noprompt don't prompt with list of objects to run against"
|
|
1160
|
+
puts " --noverify disable interactive verification of errors"
|
|
1161
|
+
puts " --verbose display rest_connection log messages"
|
|
1162
|
+
puts " --dont-check-templates don't check for script even if servers have diff. templates"
|
|
1163
|
+
puts " --quiet suppress non-essential output"
|
|
1164
|
+
puts " --version display version and exit"
|
|
1165
|
+
puts
|
|
1166
|
+
puts "chimpd options:"
|
|
1167
|
+
puts " --chimpd=<port> send jobs to chimpd listening on <port> on localhost"
|
|
1168
|
+
puts " --chimpd-wait-until-done wait until all chimpd jobs are done"
|
|
1169
|
+
puts
|
|
1170
|
+
puts "Misc Notes:"
|
|
1171
|
+
puts " * If you leave the name of a --script or --ssh command blank, chimp will prompt you"
|
|
1172
|
+
puts " * You cannot operate on array instances by selecting them with tag queries"
|
|
1173
|
+
puts " * URIs must be API URIs in the format https://my.rightscale.com/api/acct/<acct>/ec2_server_templates/<id>"
|
|
1174
|
+
puts " * The following reporting keywords can be used: nickname, ip-address, state, server_type, href"
|
|
1175
|
+
puts " server_template_href, deployment_href, created_at, updated_at"
|
|
1176
|
+
puts
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
end
|
|
1180
|
+
end
|
|
1181
|
+
|