require "rcmd/version" require "rcmd/db" require 'net/ssh' require 'thread' =begin rdoc This module is for parellel remote (SSH) execution of a single command string on Multiple hosts. The module itself consists of one method for execution (Rcmd.run_command) which does not accept any arguments. The required arguments are set as variables through the use of accessors. =end module Rcmd STDOUT.sync = true STDERR.sync = true @queue = Queue.new =begin rdoc Self class for setting up accessors =end class << self # Prefered/requested number of threads to be used. (Manditory) attr_accessor :nthreads # What user should we connect as. (Manditory) attr_accessor :user # An array of hosts to run the given command on. (Manditory) attr_accessor :host_list # String containing the command to be used. (Manditory) attr_accessor :command # Boolean for disabling STDOUT output. SDTERR is always displayed. (Optional) attr_accessor :quiet # Array containing the current iterations thread objects. attr_accessor :threads # Boolean for debug output attr_accessor :debug # Boolean for using only ssh-keys or not attr_accessor :keys_only # String fo path to user_known_hosts_file attr_accessor :hosts_file end # Main method for this module which should be called after the correct variables have been set. # # We iterate over the host list until it is empty, creating all # needed threads based upon the prefered number of threads for # execution. Before creating the threads, the method first checks # if the preferred number of threads is greater then the number of # hosts remaining in the host list. If false (threads > num hosts) # then the number of remaining hosts becomes the thread count. This # prevents spawning of unneeded threads. # # ==== Manditory values to be set # # * +:user+ - User to run the command as # * +:command+ - Command to be executed # * +:host_list+ - Array containing the hosts for command execution. # * +:nthreads+ - Preferred max number of threads # # ==== Optional Values # # * +:quiet+ - Do not print to STDOUT. STDERR is always printed # # ==== Specifically for the method only # # * +:threads+ - Array of the current threads # # ==== Example # # require 'rcmd' # Rcmd.host_list= ["host1", "host2", "host3", "host4", "host 5", "host6"] # Rcmd.user= 'root' # Rcmd.command= 'rpm -qa kernel\*' # Rcmd.nthreads= 6 # Rcmd.run_command def Rcmd.run_command() if not @command raise ArgumentError.new("No command set for execution") end if not @host_list.count >= 1 raise ArgumentError.new("host_list must contain at least one system") end @host_list.each do |host| @queue << host end until @queue.empty? # Don't start more threads then hosts. num_threads = @nthreads <= @queue.length ? @nthreads : @queue.length # Prepare threads @threads = [ ] num_threads.times do |i| @threads[i] = Thread.new { begin conn_options = { :user => @user, :host => @queue.pop, :password => nil, :quiet => @quiet, :debug => @debug, :keys_only => @keys_only, :user_known_hosts_file => @hosts_file} STDERR.print "DEBUG :: Connecting to #{conn_options[:host]}\n" if conn_options[:debug] Net::SSH.start(conn_options[:host], conn_options[:user], :password => conn_options[:passwd]) do |session| # Open channel for input/output control session.open_channel do |channel| channel.on_data do |ch, data| # Print recieved data if quiet is not true STDOUT.print "#{conn_options[:host]} :: #{data}" unless conn_options[:quiet] end channel.on_extended_data do |ch,type,data| # Always print stderr data STDERR.print "#{conn_options[:host]} :: ERROR :: #{data}" end # Execute command channel.exec @command end # Loop until command completes session.loop end rescue STDERR.print "#{conn_options[:host]} :: CONNECT ERROR :: Unable to connect to host!\n" end } # Execute threads end @threads.each { |t| t.join } end unless @threads.each.map {|t| t.alive?}.none? sleep 1 end end end