Skip to content
Browse files

+ Code imported, completely disfunctional

  • Loading branch information...
1 parent 4e2b802 commit e813aed8870f2d7f57d7b3f389cc076a003c6ce1 @kschiess committed Dec 15, 2010
Showing with 259 additions and 0 deletions.
  1. +6 −0 Gemfile
  2. +20 −0 Gemfile.lock
  3. +23 −0 LICENSE
  4. +89 −0 bin/ndo
  5. +13 −0 lib/multi_command.rb
  6. +80 −0 lib/ssh_run.rb
  7. +3 −0 spec/config/hosts.yaml
  8. +3 −0 spec/config/hosts.yaml.template
  9. +11 −0 spec/multi_command_spec.rb
  10. +11 −0 spec/spec_helper.rb
View
6 Gemfile
@@ -0,0 +1,6 @@
+source "http://rubygems.org"
+
+group :development do
+ gem 'rspec'
+ gem 'flexmock'
+end
View
20 Gemfile.lock
@@ -0,0 +1,20 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.2)
+ flexmock (0.8.11)
+ rspec (2.3.0)
+ rspec-core (~> 2.3.0)
+ rspec-expectations (~> 2.3.0)
+ rspec-mocks (~> 2.3.0)
+ rspec-core (2.3.0)
+ rspec-expectations (2.3.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.3.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ flexmock
+ rspec
View
23 LICENSE
@@ -0,0 +1,23 @@
+
+ Copyright (c) 2010 Kaspar Schiess
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation
+ files (the "Software"), to deal in the Software without
+ restriction, including without limitation the rights to use,
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the
+ Software is furnished to do so, subject to the following
+ conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ OTHER DEALINGS IN THE SOFTWARE.
View
89 bin/ndo
@@ -0,0 +1,89 @@
+#!/usr/bin/env ruby
+
+require 'thread'
+
+$:.unshift File.dirname(__FILE__) + "/../lib"
+require 'ssh_run'
+
+class Worker
+ def initialize(command, reporter, queue)
+ @command = command
+ @queue = queue
+ @reporter = reporter
+ end
+
+ def start
+ @thread = Thread.start do
+ loop do
+ host = @queue.pop
+ break if host == :shutdown
+
+ act host
+ end
+ end
+ self
+ end
+
+ def act(host)
+ begin
+ timeout(10) do
+ out, err = host.run(@command)
+ @reporter.report_sucess(host, out)
+ end
+ rescue => ex
+ @reporter.report_failure(host, ex)
+ end
+ end
+
+ def join
+ @thread.join
+ end
+end
+
+class HostReporter
+ def initialize
+ @report_mutex = Mutex.new
+ end
+
+ def report_sucess(host, output)
+ @report_mutex.synchronize do
+ print "%20s: " % host.name
+
+ clean_out = output.chomp
+ puts if clean_out.index("\n")
+ puts clean_out
+ puts if clean_out.index("\n")
+ end
+ end
+
+ def report_failure(host, exception)
+ @report_mutex.synchronize do
+ print "%20s: " % host.name
+
+ puts "FAILURE: #{exception}"
+ end
+ end
+end
+
+if ARGV.empty?
+ puts "Usage: ndo HOSTS COMMAND"
+ puts " Executes COMMAND on all HOSTS in parallel."
+ exit 1
+end
+
+reporter = HostReporter.new
+queue = Queue.new
+File.readlines(ARGV.shift).each { |line|
+ queue.push Host.new(line.chomp)
+}
+
+workers = []
+command = ARGV.join(' ')
+20.times do
+ queue.push :shutdown
+ workers << Worker.new(command, reporter, queue).start
+end
+
+workers.each do |worker|
+ worker.join
+end
View
13 lib/multi_command.rb
@@ -0,0 +1,13 @@
+
+# A class to execute a command on a list of hosts in parallel; allows access
+# to results and is thus a) multi threaded and b) Ruby 1.9.2 only.
+#
+class MultiCommand
+ def initialize(command, hosts)
+ end
+
+ # Runs the command on all hosts. Returns a result collection.
+ #
+ def run
+ end
+end
View
80 lib/ssh_run.rb
@@ -0,0 +1,80 @@
+require 'stringio'
+require 'open4'
+
+class Host
+ attr_reader :name
+ def initialize(hostname)
+ @name = hostname
+ end
+
+ class ExecutionFailure < StandardError
+ def initialize(message=nil, stdout=nil, stderr=nil)
+ super(message)
+ @stdout, @stderr = stdout, stderr
+ end
+
+ attr_reader :stdout, :stderr
+
+ def to_s
+ super + "\nstdout: #{stdout}\nstderr: #{stderr}"
+ end
+ end
+
+ def run command
+ cmd = ['ssh', name, command].flatten
+ result = []
+
+ pid, inn, out, err = Open4.popen4(*cmd)
+
+ inn.sync = true
+ streams = [out, err]
+ out_stream = {
+ out => StringIO.new,
+ err => StringIO.new,
+ }
+
+ # Handle process termination ourselves
+ status = nil
+ Thread.start do
+ status = Process.waitpid2(pid).last
+ end
+
+ until streams.empty? do
+ # don't busy loop
+ selected, = select streams, nil, nil, 0.1
+
+ next if selected.nil? or selected.empty?
+
+ selected.each do |stream|
+ if stream.eof? then
+ streams.delete stream if status # we've quit, so no more writing
+ next
+ end
+
+ data = stream.readpartial(1024)
+ out_stream[stream].write data
+ #
+ # if stream == err and data =~ sudo_prompt then
+ # inn.puts sudo_password
+ # data << "\n"
+ # $stderr.write "\n"
+ # end
+
+ result << data
+ end
+ end
+
+ unless status.success? then
+ raise ExecutionFailure.new(
+ "Command failed (#{status.inspect})",
+ *streams.map { |strm| out_stream[strm].string }
+ )
+ end
+
+ out_stream.map { |io, copy| copy.string }
+ ensure
+ inn.close rescue nil
+ out.close rescue nil
+ err.close rescue nil
+ end
+end
View
3 spec/config/hosts.yaml
@@ -0,0 +1,3 @@
+---
+ - gironimo
+ - belle
View
3 spec/config/hosts.yaml.template
@@ -0,0 +1,3 @@
+---
+ - host1
+ - host2
View
11 spec/multi_command_spec.rb
@@ -0,0 +1,11 @@
+
+require 'spec_helper'
+
+describe MultiCommand do
+ let(:multi_cmd) { MultiCommand.new('uname -a', %w(a b)) }
+
+end
+
+describe MultiCommand, 'integration' do
+ let(:multi_cmd) { MultiCommand.new('uname -a', spec_hosts) }
+end
View
11 spec/spec_helper.rb
@@ -0,0 +1,11 @@
+require 'yaml'
+
+def spec_file(*names)
+ File.join(
+ File.dirname(__FILE__),
+ *names)
+end
+
+def spec_hosts
+ YAML.load_file(spec_file('config', 'hosts.yaml'))
+end

0 comments on commit e813aed

Please sign in to comment.
Something went wrong with that request. Please try again.