Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(BKR-71) Adding support to use WinRM for windows platform #805

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions beaker.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'net-scp', '~> 1.2'
s.add_runtime_dependency 'inifile', '~> 2.0'
s.add_runtime_dependency 'rsync', '~> 1.0.9'
s.add_runtime_dependency 'winrm', '~> 1.3.0'
s.add_runtime_dependency 'winrm-fs', '~> 0.1.0'

# Optional provisioner specific support
s.add_runtime_dependency 'rbvmomi', '~> 1.8'
Expand Down
9 changes: 7 additions & 2 deletions lib/beaker/host.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'benchmark'
require 'rsync'

[ 'command', 'ssh_connection' ].each do |lib|
[ 'command', 'ssh_connection', 'winrm_connection' ].each do |lib|
require "beaker/#{lib}"
end

Expand Down Expand Up @@ -358,9 +358,14 @@ def delete_env_var key, val
end

def connection
@connection ||= SshConnection.connect( reachable_name,
if self[:platform] =~ /windows-/ and self[:communicator] == 'winrm'
@connection ||= WinrmConnection.connect( reachable_name,
self['winrm'], { :logger => @logger } )
else
@connection ||= SshConnection.connect( reachable_name,
self['user'],
self['ssh'], { :logger => @logger } )
end
end

def close
Expand Down
5 changes: 5 additions & 0 deletions lib/beaker/options/presets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,11 @@ def presets
:forward_agent => true,
:keys => ["#{ENV['HOME']}/.ssh/id_rsa"],
:user_known_hosts_file => "#{ENV['HOME']}/.ssh/known_hosts",
},
:winrm => {
:auth => 'kerberos',
:timeout => 300,
:port => 5985
}
})
end
Expand Down
184 changes: 184 additions & 0 deletions lib/beaker/winrm_connection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
require 'socket'
require 'timeout'
require 'winrm'
require 'winrm-fs'

module Beaker
class WinrmConnection

attr_accessor :logger

RETRYABLE_EXCEPTIONS = [
SocketError,
Timeout::Error,
Errno::ETIMEDOUT,
Errno::EHOSTDOWN,
Errno::EHOSTUNREACH,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
Errno::ENETUNREACH,
IOError,
]

def initialize hostname, winrm_opts = {}, options ={}
@hostname = hostname
@winrm_opts = winrm_opts
@logger = options[:logger]
@options = options
end

def self.connect hostname, winrm_opts = {}, options ={}
connection = new hostname, winrm_opts, options
connection.connect
connection
end

# connect to the host
def connect
try = 1
last_wait = 0
wait = 1
@winrm ||= begin
@logger.debug "Attempting winrm connection to #{@hostname}, opts: #{@winrm_opts}"
endpoint = "http://#{@hostname}:5985/wsman"
auth = @winrm_opts['auth']

case auth
when 'kerberos'
krb5_realm = @winrm_opts['realm']
WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => krb5_realm)
when 'plaintext'
user = @winrm_opts['user']
pass = @winrm_opts['pass']
WinRM::WinRMWebService.new(endpoint, :plaintext, :user => user, :pass => pass, :basic_auth_only => true)
when 'ssl'
user = @winrm_opts['user']
pass = @winrm_opts['pass']
ca_trust_path = @winrm_opts['ca_trust_path']
WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :ca_trust_path => ca_trust_path, :basic_auth_only => true)
else
@logger.error "Invalid authentication method #{auth}, supported: kerberos, plaintext, ssl."
raise
end
rescue *RETRYABLE_EXCEPTIONS => e
if try <= 11
@logger.warn "Try #{try} -- Host #{@hostname} unreachable: #{e.class.name} - #{e.message}"
@logger.warn "Trying again in #{wait} seconds"
sleep wait
(last_wait, wait) = wait, last_wait + wait
try += 1
retry
else
@logger.error "Failed to connect to #{@hostname}"
raise
end
end
end

# closes this winrmConnection
def close
begin
if @winrm and @sid
@winrm.close_shell(@sid)
else
@logger.warn("winrm.close: connection is already closed, no action needed")
end
rescue *RETRYABLE_EXCEPTIONS => e
@logger.warn "Attemped winrm.close, (caught #{e.class.name} - #{e.message})."
rescue => e
@logger.warn "winrm.close threw unexpected Error: #{e.class.name} - #{e.message}. Shutting down, and re-raising error below"
raise e
ensure
@winrm = nil
@logger.warn("winrm connection to #{@hostname} has been terminated")
end
end

def execute command, options= {}, stdout
try = 1
wait = 1
last_wait = 0
output = WinRM::Output.new
result = Result.new(@hostname, command)

begin
# ensure that we have a current connection object
connect
@sid = @winrm.open_shell
if @sid
@logger.info "Open a shell on #{@hostname} for #{command.inspect}"
cmd_id = @winrm.run_command(@sid, command)
output = @winrm.get_command_output(@sid, cmd_id)
result.stdout = output.stdout
result.stderr = output.stderr
result.exit_code = output[:exitcode]

@logger.debug "STDOUT: #{result.stdout}" unless result.stdout.nil? || result.stdout == ''
@logger.debug "STDERR: #{result.stderr}" unless result.stderr.nil? || result.stderr == ''
@logger.debug "Exit Code: #{result.exit_code}" unless result.exit_code.nil? || result.exit_code == ''

else
abort "FAILED: could not open a shell on #{@hostname} for #{command.inspect}"
end
rescue *RETRYABLE_EXCEPTIONS => e
if try < 11
sleep wait
(last_wait, wait) = wait, last_wait + wait
try += 1
@logger.error "Command execution '#{@hostname}$ #{command}' failed (#{e.class.name} - #{e.message})"
close
@logger.debug "Preparing to retry: closed winrm object"
retry
else
raise
end
end
result.finalize!
@logger.last_result = result
result
# Close the shell to avoid the quota of 5 concurrent shells for a user by default.
close
@sid = nil
end

def scp_to source, target, options = {}, dry_run = false
return if dry_run

result = Result.new(@hostname, [source, target])
result.stdout = "\n"
file_manager = WinRM::FS::FileManager.new(@winrm)
file_manager.upload(source, target) do |bytes_copied, total_bytes, local_path, remote_path|
result.stdout << "\t#{bytes_copied}bytes of #{total_bytes}bytes copied\n"
end

# Setting these values allows reporting via result.log(test_name)
result.stdout << " Copied file #{source} to #{@hostname}:#{target}"

# Net::Scp always returns 0, so just set the return code to 0.
result.exit_code = 0

result.finalize!
return result
end

def scp_from source, target, options = {}, dry_run = false
return if dry_run

result = Result.new(@hostname, [source, target])
result.stdout = "\n"
file_manager = WinRM::FS::FileManager.new(@winrm)
file_manager.download(source, target) do |bytes_copied, total_bytes, local_path, remote_path|
result.stdout << "\t#{bytes_copied}bytes of #{total_bytes}bytes copied\n"
end

# Setting these values allows reporting via result.log(test_name)
result.stdout << " Copied file #{@hostname}:#{source} to #{target}"

# Net::Scp always returns 0, so just set the return code to 0.
result.exit_code = 0

result.finalize!
result
end
end
end