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