lib/chef_metal/transport/ssh.rb in chef-metal-0.9.1 vs lib/chef_metal/transport/ssh.rb in chef-metal-0.9.2

- old
+ new

@@ -23,38 +23,39 @@ def execute(command, execute_options = {}) Chef::Log.info("Executing #{options[:prefix]}#{command} on #{username}@#{host}") stdout = '' stderr = '' exitstatus = nil - channel = session.open_channel do |channel| - # Enable PTY unless otherwise specified, some instances require this - unless options[:ssh_pty_enable] == false - channel.request_pty do |chan, success| - raise "could not get pty" if !success && options[:ssh_pty_enable] + session # grab session outside timeout, it has its own timeout + with_execute_timeout(execute_options) do + channel = session.open_channel do |channel| + # Enable PTY unless otherwise specified, some instances require this + unless options[:ssh_pty_enable] == false + channel.request_pty do |chan, success| + raise "could not get pty" if !success && options[:ssh_pty_enable] + end end - end - channel.exec("#{options[:prefix]}#{command}") do |ch, success| - raise "could not execute command: #{command.inspect}" unless success + channel.exec("#{options[:prefix]}#{command}") do |ch, success| + raise "could not execute command: #{command.inspect}" unless success - channel.on_data do |ch2, data| - stdout << data - stream_chunk(execute_options, data, nil) - end + channel.on_data do |ch2, data| + stdout << data + stream_chunk(execute_options, data, nil) + end - channel.on_extended_data do |ch2, type, data| - stderr << data - stream_chunk(execute_options, nil, data) - end + channel.on_extended_data do |ch2, type, data| + stderr << data + stream_chunk(execute_options, nil, data) + end - channel.on_request "exit-status" do |ch, data| - exitstatus = data.read_long + channel.on_request "exit-status" do |ch, data| + exitstatus = data.read_long + end end end - end - with_execute_timeout(execute_options) do channel.wait end Chef::Log.info("Completed #{command} on #{username}@#{host}: exit status #{exitstatus}") Chef::Log.debug("Stdout was:\n#{stdout}") if stdout != '' && !options[:stream] && !options[:stream_stdout] && Chef::Config.log_level != :debug @@ -125,23 +126,29 @@ @session = nil end end def available? - execute('pwd') + # If you can't pwd within 10 seconds, you can't pwd + execute('pwd', :timeout => 10) true - rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, Net::SSH::AuthenticationFailed, Net::SSH::Disconnect, Net::SSH::HostKeyMismatch + rescue Timeout::Error, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::ECONNRESET, Net::SSH::AuthenticationFailed, Net::SSH::Disconnect, Net::SSH::HostKeyMismatch Chef::Log.debug("#{username}@#{host} unavailable: could not execute 'pwd' on #{host}: #{$!.inspect}") false end protected def session @session ||= begin Chef::Log.debug("Opening SSH connection to #{username}@#{host} with options #{ssh_options.inspect}") - Net::SSH.start(host, username, ssh_options) + # Small initial connection timeout (10s) to help us fail faster when server is just dead + begin + Net::SSH.start(host, username, { :timeout => 10 }.merge(ssh_options)) + rescue Timeout::Error + raise InitialConnectTimeout.new($!) + end end end def download(path, local_path) channel = Net::SCP.new(session).download(path, local_path) @@ -180,9 +187,18 @@ # TODO stdout/stderr is already printed at info/debug level. Let's not print it twice, it's a lot. msg = "Error: command '#{command}' exited with code #{exitstatus}.\n" raise msg end end + end + + class InitialConnectTimeout < Timeout::Error + def initialize(original_error) + super(original_error.message) + @original_error = original_error + end + + attr_reader :original_error end end end end