mssh 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/mssh +1 -1
  2. metadata +4 -6
  3. data/bin/mcmd-ev +0 -97
  4. data/lib/mcmd-ev.rb +0 -390
data/bin/mssh CHANGED
@@ -56,7 +56,7 @@ if (!options[:range].nil?)
56
56
  end
57
57
  if (!options[:file].nil?)
58
58
  targets_fd = File.open(options[:file])
59
- targets_fd.read.each { |x| targets << x.chomp }
59
+ targets_fd.read.each_line { |x| targets << x.chomp }
60
60
  end
61
61
  if (!options[:hostlist].nil?)
62
62
  targets.push *options[:hostlist]
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mssh
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
9
+ - 6
10
+ version: 0.0.6
11
11
  platform: ruby
12
12
  authors:
13
13
  - Evan Miller
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-09-04 00:00:00 Z
18
+ date: 2012-10-02 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: json
@@ -42,10 +42,8 @@ extensions: []
42
42
  extra_rdoc_files:
43
43
  - LICENSE.md
44
44
  files:
45
- - lib/mcmd-ev.rb
46
45
  - lib/mcmd.rb
47
46
  - bin/mcmd
48
- - bin/mcmd-ev
49
47
  - bin/mssh
50
48
  - README.md
51
49
  - LICENSE.md
data/bin/mcmd-ev DELETED
@@ -1,97 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- require 'pp'
4
-
5
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
6
- require 'mcmd-ev'
7
-
8
- require 'optparse'
9
- options = {
10
- :maxflight => 200,
11
- :timeout => 60,
12
- :global_timeout => 0,
13
- }
14
-
15
- optparse = OptionParser.new do |opts|
16
- opts.on('-r', '--range RANGE', 'currently takes a CSV list') do |arg|
17
- options[:range] = arg
18
- end
19
- opts.on('-m', '--maxflight 50', 'How many subprocesses? 50 by default') do |arg|
20
- options[:maxflight] = arg
21
- end
22
- opts.on('-t', '--timeout 60', 'How many seconds may each individual process take? 0 for no timeout') do |arg|
23
- options[:timeout] = arg
24
- end
25
- opts.on('-g', '--global_timeout 600', 'How many seconds for the whole shebang 0 for no timeout') do |arg|
26
- options[:global_timeout] = arg
27
- end
28
- opts.on('--noshell', "Don't invoke a shell. Args will be passed to exec verbatim ") do |arg|
29
- options[:noshell] = arg
30
- end
31
- opts.on('-c', '--collapse', "Collapse similar output ") do |arg|
32
- options[:collapse] = arg
33
- end
34
- opts.on('-v', '--verbose', "Verbose output") do |arg|
35
- options[:verbose] = arg
36
- end
37
- opts.on('-d', '--debug', "Debug output") do |arg|
38
- options[:debug] = arg
39
- end
40
- # option to merge stdin/stdout into one buf? how should this work?
41
- # option to ignore as-we-go yield output - this is off by default now except for success/fail
42
- end
43
- optparse.parse!
44
-
45
- raise "Error, need -r argument" if options[:range].nil? or options[:range].empty?
46
- raise "Error, need command to run" if ARGV.size.zero?
47
-
48
- m = MultipleCmd.new
49
-
50
- targets = options[:range].split ","
51
-
52
- m.commands = targets.map { |t| ["/bin/sh", "-c"].push ARGV.map { |arg| arg.gsub('HOSTNAME', t)}.join " " }
53
- command_to_target = Hash.new
54
- targets.size.times do |i|
55
- command_to_target[m.commands[i].object_id] = targets[i]
56
- end
57
- m.yield_startcmd = lambda { |p| puts "#{command_to_target[p.command.object_id]}: starting" } if options[:verbose]
58
- m.yield_wait = lambda { |p| puts "#{p.success? ? 'SUCCESS' : 'FAILURE'} #{command_to_target[p.command.object_id]}: '#{p.stdout_buf}'" }
59
- # m.yield_proc_timeout = lambda { |p| puts "am killing #{p.inspect}"}
60
-
61
- m.perchild_timeout = options[:timeout].to_i
62
- m.global_timeout = options[:global_timeout].to_i
63
- m.maxflight = options[:maxflight].to_i
64
- m.verbose = options[:verbose]
65
- m.debug = options[:debug]
66
-
67
- result = m.run
68
-
69
- if options[:collapse]
70
- # print a collapsed summary
71
- stdout_matches_success = Hash.new
72
- stdout_matches_failure = Hash.new
73
- result.each do |r|
74
- if r[:retval].success?
75
- stdout_matches_success[r[:stdout_buf]] = [] if stdout_matches_success[r[:stdout_buf]].nil?
76
- stdout_matches_success[r[:stdout_buf]] << command_to_target[r[:command].object_id]
77
- else
78
- stdout_matches_failure[r[:stdout_buf]] = [] if stdout_matches_failure[r[:stdout_buf]].nil?
79
- stdout_matches_failure[r[:stdout_buf]] << command_to_target[r[:command].object_id]
80
- end
81
- end
82
- # output => [targets ...]
83
- stdout_matches_success.each_pair do |k,v|
84
- puts "SUCCESS: #{v.join ','}: #{k}"
85
- end
86
- stdout_matches_failure.each_pair do |k,v|
87
- puts "FAILURE: #{v.join ','}: #{k}"
88
- end
89
- else
90
- # we already printed while in-flight; do nothing
91
- # not collapse, print one per host
92
- # result.each do |r|
93
- # target = command_to_target[r[:command].object_id]
94
- # puts "#{target}: '#{r[:stdout_buf].chomp}'\n"
95
- # end
96
- end
97
-
data/lib/mcmd-ev.rb DELETED
@@ -1,390 +0,0 @@
1
- #!/usr/bin/ruby
2
-
3
- require 'pp'
4
- require 'rubygems'
5
- require 'eventmachine'
6
-
7
-
8
- # TODO - figure out why IO.sysread isn't raising on EOF
9
- module FdWatcher
10
- def notify_readable
11
- puts "readable: '#{@io.inspect}'"
12
- puts "mcmd obj: '#{@mcmd}'"
13
- # read available bytes, add to the subproc's read buf
14
- if not @mcmd.subproc_by_fd.has_key?(@io)
15
- raise "Select returned a fd which I have not seen! fd: #{@io.inspect}"
16
- end
17
- subproc = @mcmd.subproc_by_fd[@io]
18
- buf = ""
19
- begin
20
- buf = @io.sysread(4096)
21
- puts "saw #{buf}"
22
- if buf.nil?
23
- raise " Impossible result from sysread()"
24
- end
25
- # no exception? bytes were read. append them.
26
- if @io == subproc.stdout_fd
27
- subproc.stdout_buf << buf
28
- # FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
29
- if subproc.stdout_buf.bytesize > @mcmd.max_read_size
30
- # self.kill_process(subproc) # can't kill this here, need a way to mark-to-kill
31
- end
32
- elsif @io == subproc.stderr_fd
33
- subproc.stderr_buf << buf
34
- # FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
35
- if subproc.stderr_buf.bytesize > @mcmd.max_read_size
36
- # self.kill_process(subproc) # "" above
37
- end
38
- end
39
- rescue SystemCallError, EOFError => ex
40
- puts "DEBUG: saw read exception #{ex}"
41
- @mcmd.periodic
42
- rescue => ex
43
- puts "else #{ex.inspect}"
44
- end
45
- end
46
-
47
- def unbind
48
- puts "GOT UNBIND"
49
- @mcmd.periodic
50
- # move periodic() bits under here, to be called as necessary
51
- end
52
- def notify_writable
53
- # puts "writable: '#{@io.inspect}'"
54
- end
55
- end
56
-
57
- class FdClass < EventMachine::Connection
58
- include FdWatcher
59
- end
60
-
61
- module ProcessWatcher
62
- def process_exited
63
- puts 'the forked child died!'
64
- EM.stop_event_loop
65
- puts "stopped"
66
- end
67
- end
68
-
69
-
70
- class MultipleCmd
71
-
72
- attr_accessor :global_timeout, :maxflight, :perchild_timeout, :commands
73
- attr_accessor :yield_wait, :yield_startcmd, :debug, :yield_proc_timeout
74
- attr_accessor :verbose, :poll_period, :max_read_size
75
- attr_accessor :subproc_by_fd
76
-
77
- def initialize
78
- # these are re-initialized after every run
79
- @subproc_by_pid = Hash.new
80
- @subproc_by_fd = Hash.new
81
- @processed_commands = []
82
- # end items which are re-initialized
83
-
84
- self.commands = []
85
- self.perchild_timeout = 60
86
- self.global_timeout = 0
87
- self.maxflight = 200
88
- self.debug = false
89
- self.poll_period = 0.5 # shouldn't need adjusting
90
- self.max_read_size = 2 ** 19 # 512k
91
- end
92
-
93
- def noshell_exec(cmd)
94
- if cmd.length == 1
95
- Kernel.exec([cmd[0], cmd[0]])
96
- else
97
- Kernel.exec([cmd[0], cmd[0]], *cmd[1..-1])
98
- end
99
- end
100
-
101
- # I should probably move this whole method
102
- # into SubProc and make the subproc_by_* into
103
- # class variables
104
- def add_subprocess(cmd)
105
- stdin_rd, stdin_wr = IO.pipe
106
- stdout_rd, stdout_wr = IO.pipe
107
- stderr_rd, stderr_wr = IO.pipe
108
- subproc = MultipleCmd::SubProc.new
109
- subproc.stdin_fd = stdin_wr
110
- subproc.stdout_fd = stdout_rd
111
- subproc.stderr_fd = stderr_rd
112
- subproc.command = cmd
113
-
114
- puts "Before fork"
115
- pid = fork
116
- if not pid.nil?
117
- # parent
118
- stdin_rd.close rescue true
119
- stdout_wr.close rescue true
120
- stderr_wr.close rescue true
121
- # for mapping to subproc by pid
122
- subproc.pid = pid
123
- @subproc_by_pid[pid] = subproc
124
- # for mapping to subproc by i/o handle (returned from select)
125
- @subproc_by_fd[stdin_rd] = subproc
126
- @subproc_by_fd[stdin_wr] = subproc
127
- @subproc_by_fd[stdout_rd] = subproc
128
- @subproc_by_fd[stdout_wr] = subproc
129
- @subproc_by_fd[stderr_rd] = subproc
130
- @subproc_by_fd[stderr_wr] = subproc
131
-
132
- self.yield_startcmd.call(subproc) unless self.yield_startcmd.nil?
133
- temp_self = self
134
- # now add fds to EM
135
- EM.watch(stdin_wr, FdClass) { |c|
136
- c.notify_writable = true
137
- c.instance_eval do
138
- @mcmd = temp_self
139
- end
140
- }
141
- EM.watch(stdout_rd, FdClass) { |c|
142
- c.notify_readable = true
143
- c.instance_eval do
144
- @mcmd = temp_self
145
- end
146
- }
147
- EM.watch(stderr_rd, FdClass) { |c|
148
- c.notify_readable = true
149
- c.instance_eval do
150
- @mcmd = temp_self
151
- end
152
- }
153
- # EM.watch_process(pid, ProcessWatcher) # is this kqueue only? If so, probably don't need it FIXME
154
- else
155
- # child
156
- # setup stdin, out, err
157
- stdin_wr.close rescue true
158
- stdout_rd.close rescue true
159
- stderr_rd.close rescue true
160
- STDIN.reopen(stdin_rd)
161
- STDOUT.reopen(stdout_wr)
162
- STDERR.reopen(stderr_wr)
163
- puts "EXEC: #{cmd}"
164
- noshell_exec(cmd)
165
- raise "can't be reached!!. exec failed!!"
166
- end
167
- end
168
-
169
- def process_read_fds(read_fds)
170
- read_fds.each do |fd|
171
- begin
172
- buf = fd.sysread(4096)
173
-
174
- if buf.nil?
175
- raise " Impossible result from sysread()"
176
- end
177
- # no exception? bytes were read. append them.
178
- if fd == subproc.stdout_fd
179
- subproc.stdout_buf << buf
180
- # FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
181
- if subproc.stdout_buf.bytesize > self.max_read_size
182
- # self.kill_process(subproc) # can't kill this here, need a way to mark-to-kill
183
- end
184
- elsif fd == subproc.stderr_fd
185
- subproc.stderr_buf << buf
186
- # FIXME if we've read > maxbuf, allow closing/ignoring the fd instead of hard kill
187
- if subproc.stderr_buf.bytesize > self.max_read_size
188
- # self.kill_process(subproc) # "" above
189
- end
190
- end
191
- rescue SystemCallError, EOFError => ex
192
- puts "DEBUG: saw read exception #{ex}" if self.debug
193
- detach
194
- # clear out the read fd for this subproc
195
- # finalize read i/o
196
- # if we're reading, it was the process's stdout or stderr
197
- if fd == subproc.stdout_fd
198
- subproc.stdout_fd = nil
199
- elsif fd == subproc.stderr_fd
200
- subproc.stderr_fd = nil
201
- else
202
- raise "impossible: operating on a subproc where the fd isn't found, even though it's mapped"
203
- end
204
- fd.close rescue true
205
- end
206
- end
207
- end # process_read_fds()
208
- def process_write_fds(write_fds)
209
- write_fds.each do |fd|
210
- raise "working on an unknown fd #{fd}" unless @subproc_by_fd.has_key?(fd)
211
- subproc = @subproc_by_fd[fd]
212
- buf = ""
213
- # add writing here, todo. not core feature
214
- end
215
- end
216
- def process_err_fds(err_fds)
217
- end
218
-
219
- # iterate and service fds in child procs, collect data and status
220
- def service_subprocess_io
221
- write_fds = @subproc_by_pid.values.select {|x| not x.stdin_fd.nil? and not x.terminated}.map {|x| x.stdin_fd}
222
- read_fds = @subproc_by_pid.values.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
223
-
224
- read_fds, write_fds, err_fds = IO.select(read_fds, write_fds, nil, self.poll_period)
225
-
226
- self.process_read_fds(read_fds) unless read_fds.nil?
227
- self.process_write_fds(write_fds) unless write_fds.nil?
228
- self.process_err_fds(err_fds) unless err_fds.nil?
229
- # errors?
230
- end
231
-
232
- def process_timeouts
233
- now = Time.now.to_i
234
- @subproc_by_pid.values.each do |p|
235
- if (now - p.time_start) > self.perchild_timeout
236
- # expire this child process
237
-
238
- self.yield_proc_timeout.call(p) unless self.yield_proc_timeout.nil?
239
- self.kill_process(p)
240
- end
241
- end
242
- end
243
-
244
- def kill_process(p)
245
- # do not remove from pid list until waited on
246
- @subproc_by_fd.delete(p.stdin_fd)
247
- @subproc_by_fd.delete(p.stdout_fd)
248
- @subproc_by_fd.delete(p.stderr_fd)
249
- # must kill after deleting from maps
250
- # kill closes fds
251
- p.kill
252
- end
253
-
254
- def periodic
255
- puts "in periodic"
256
- self.wait
257
- # This could probably move into the child-cleanup callback
258
- # start up as many as maxflight processes
259
- while @subproc_by_pid.length < self.maxflight and not @commands.empty?
260
- # take one from @commands and start it
261
- commands = @commands.shift
262
- self.add_subprocess(commands)
263
- end
264
-
265
- puts "have #{@subproc_by_pid.length} left to go"
266
- # if we have nothing in flight (active pid)
267
- # and nothing pending on the input list
268
- # then we're done
269
- if @subproc_by_pid.length.zero? and @commands.empty?
270
- done = true
271
- puts "stopping ev loop"
272
- EM.stop_event_loop
273
- end
274
- end
275
-
276
- def run
277
- @global_time_start = Time.now.to_i
278
- done = false
279
- EM.run do
280
- EM.schedule do
281
- puts "schedule!"
282
- self.periodic
283
- end
284
- # EM.add_periodic_timer(0.5) do
285
- # puts "TIMER!!"
286
- # self.periodic
287
- # end # end periodic timer
288
- end
289
-
290
- ## These things get broken up into individual proc/fd callbacks
291
- # service running processes
292
- # self.service_subprocess_io
293
- # timeout overdue processes
294
- # self.process_timeouts
295
- # service process cleanup
296
-
297
- ## this is still the same, post-event-loop
298
- data = self.return_rundata
299
- # these are re-initialized after every run
300
- @subproc_by_pid = Hash.new
301
- @subproc_by_fd = Hash.new
302
- @processed_commands = []
303
- # end items which are re-initialized
304
- return data
305
- end
306
-
307
- def return_rundata
308
- data = []
309
- @processed_commands.each do |c|
310
- #FIXME pass through the process object
311
- data << {
312
- :pid => c.pid,
313
- :write_buf_position => c.write_buf_position,
314
- :stdout_buf => c.stdout_buf,
315
- :stderr_buf => c.stderr_buf,
316
- :command => c.command,
317
- :time_start => c.time_start,
318
- :time_end => c.time_end,
319
- :retval => c.retval,
320
- }
321
- end
322
- return data
323
- end
324
-
325
- def wait
326
- possible_children = true
327
- just_reaped = Array.new
328
- while possible_children
329
- begin
330
- pid = Process::waitpid(-1, Process::WNOHANG)
331
- if pid.nil?
332
- possible_children = false
333
- else
334
- # pid is now gone. remove from subproc_by_pid and
335
- # add to the processed commands list
336
- p = @subproc_by_pid[pid]
337
- p.time_end = Time.now.to_i
338
- p.retval = $?
339
- @subproc_by_pid.delete(pid)
340
- @processed_commands << p
341
- just_reaped << p
342
- end
343
- rescue Errno::ECHILD => ex
344
- # ECHILD. ignore.
345
- possible_children = false
346
- end
347
- end
348
- # We may have waited on a child before reading all its output. Collect those missing bits. No blocking.
349
- if not just_reaped.empty?
350
- read_fds = just_reaped.select {|x| not x.terminated}.map {|x| [x.stdout_fd, x.stderr_fd].select {|x| not x.nil? } }.flatten
351
- read_fds, write_fds, err_fds = IO.select(read_fds, nil, nil, 0)
352
- self.process_read_fds(read_fds) unless read_fds.nil?
353
- end
354
- just_reaped.each do |p|
355
- self.yield_wait.call(p) unless self.yield_wait.nil?
356
- end
357
- end
358
-
359
- end
360
-
361
- class MultipleCmd::SubProc
362
- attr_accessor :stdin_fd, :stdout_fd, :stderr_fd, :write_buf_position
363
- attr_accessor :time_start, :time_end, :pid, :retval, :stdout_buf, :stderr_buf, :command, :terminated
364
-
365
- def initialize
366
- self.write_buf_position = 0
367
- self.time_start = Time.now.to_i
368
- self.stdout_buf = ""
369
- self.stderr_buf = ""
370
- self.terminated = false
371
- end
372
-
373
- # when a process has out-stayed its welcome
374
- def kill
375
- self.stdin_fd.close rescue true
376
- self.stdout_fd.close rescue true
377
- self.stderr_fd.close rescue true
378
- #TODO configurable sig?
379
- Process::kill("KILL", self.pid)
380
- self.terminated = true
381
- end
382
-
383
-
384
- # some heuristic to determine if this job was successful
385
- # for now, trust retval. Also check stderr?
386
- def success?
387
- self.retval.success?
388
- end
389
- end
390
-