Permalink
Browse files

Merge pull request #126 from joshcooper/ticket/master/11944-separate-…

…host-logic

(#11944) Separate native command logic from acceptance tests
  • Loading branch information...
djm68 committed Feb 3, 2012
2 parents e36f0d2 + 64e3330 commit f9cb52b2463c255a417c78ff7820ae09911b4ec9
View
@@ -1,6 +1,8 @@
# An immutable data structure representing a task to run on a remote
# machine.
class Command
+ include Test::Unit::Assertions
+
def initialize(command_string)
@command_string = command_string
end
@@ -12,7 +14,24 @@ def cmd_line(host_info)
end
def exec(host, options={})
- host.exec(cmd_line(host), options)
+ options[:acceptable_exit_codes] ||= [0]
+ options[:failing_exit_codes] ||= [1]
+
+ cmdline = cmd_line(host)
+ result = host.exec(cmdline, options)
+
+ unless options[:silent] then
+ result.log
+ if options[:acceptable_exit_codes].include?(result.exit_code)
+ # cool.
+ elsif options[:failing_exit_codes].include?(result.exit_code)
+ assert( false, "Host '#{host} exited with #{result.exit_code} running: #{cmdline}" )
+ else
+ raise "Host '#{host}' exited with #{result.exit_code} running: #{cmdline}"
+ end
+ end
+
+ result
end
# Determine the appropriate puppet env command for the given host.
View
@@ -0,0 +1,19 @@
+module CommandFactory
+ include Test::Unit::Assertions
+
+ def execute(command, options={}, &block)
+ command = Command.new(command)
+
+ result = command.exec(self, options)
+
+ if block_given?
+ yield result
+ else
+ result.stdout.chomp
+ end
+ end
+
+ def fail_test(msg)
+ assert(false, msg)
+ end
+end
View
@@ -0,0 +1,114 @@
+class Host
+ def self.create(name, overrides, defaults)
+ case overrides['platform']
+ when /windows/;
+ Windows::Host.new(name, overrides, defaults)
+ else
+ Unix::Host.new(name, overrides, defaults)
+ end
+ end
+
+ # A cache for active SSH connections to our execution nodes.
+ def initialize(name, overrides, defaults)
+ @name,@overrides,@defaults = name,overrides,defaults
+ end
+ def []=(k,v)
+ @overrides[k] = v
+ end
+ def [](k)
+ @overrides.has_key?(k) ? @overrides[k] : @defaults[k]
+ end
+ def to_str
+ @name
+ end
+ def to_s
+ @name
+ end
+ def +(other)
+ @name+other
+ end
+
+ attr_reader :name, :overrides
+
+ # Wrap up the SSH connection process; this will cache the connection and
+ # allow us to reuse it for each operation without needing to reauth every
+ # single time.
+ def ssh
+ tries = 1
+ @ssh ||= begin
+ Net::SSH.start(self, self['user'], self['ssh'])
+ rescue
+ tries += 1
+ if tries < 4
+ puts "Try #{tries} -- Host Unreachable"
+ puts 'Trying again in 20 seconds'
+ sleep 20
+ retry
+ end
+ end
+ end
+
+ def close
+ if @ssh
+ @ssh.close
+ end
+ end
+
+ def do_action(verb,*args)
+ result = Result.new(self,args,'','',0)
+ Log.debug "#{self}: #{verb}(#{args.inspect})"
+ yield result unless $dry_run
+ result
+ end
+
+ def exec(command, options)
+ do_action('RemoteExec',command) do |result|
+ ssh.open_channel do |channel|
+ if options[:pty] then
+ channel.request_pty do |ch, success|
+ if success
+ puts "Allocated a PTY on #{@name} for #{command.inspect}"
+ else
+ abort "FAILED: could not allocate a pty when requested on " +
+ "#{@name} for #{command.inspect}"
+ end
+ end
+ end
+
+ channel.exec(command) do |terminal, success|
+ abort "FAILED: to execute command on a new channel on #{@name}" unless success
+ terminal.on_data { |ch, data| result.stdout << data }
+ terminal.on_extended_data { |ch, type, data| result.stderr << data if type == 1 }
+ terminal.on_request("exit-status") { |ch, data| result.exit_code = data.read_long }
+
+ # queue stdin data, force it to packets, and signal eof: this
+ # triggers action in many remote commands, notably including
+ # 'puppet apply'. It must be sent at some point before the rest
+ # of the action.
+ terminal.send_data(options[:stdin].to_s)
+ terminal.process
+ terminal.eof!
+ end
+ end
+ # Process SSH activity until we stop doing that - which is when our
+ # channel is finished with...
+ ssh.loop
+ end
+ end
+
+ def do_scp(source, target)
+ do_action("ScpFile",source,target) { |result|
+ # Net::Scp always returns 0, so just set the return code to 0 Setting
+ # these values allows reporting via result.log(test_name)
+ result.stdout = "SCP'ed file #{source} to #{@host}:#{target}"
+ result.stderr=nil
+ result.exit_code=0
+ recursive_scp='false'
+ recursive_scp='true' if File.directory? source
+ ssh.scp.upload!(source, target, :recursive => recursive_scp)
+ }
+ end
+end
+
+require 'lib/host/windows'
+require 'lib/host/unix'
View
@@ -0,0 +1,37 @@
+require 'lib/host'
+require 'lib/command_factory'
+
+module Unix
+ class Host < Host
+ require 'lib/host/unix/user'
+ require 'lib/host/unix/group'
+ require 'lib/host/unix/file'
+ require 'lib/host/unix/exec'
+
+ include Unix::User
+ include Unix::Group
+ include Unix::File
+ include Unix::Exec
+
+ PE_DEFAULTS = {
+ 'user' => 'root',
+ 'puppetpath' => '/etc/puppetlabs/puppet',
+ 'puppetbin' => '/usr/local/bin/puppet',
+ 'puppetbindir' => '/opt/puppet/bin'
+ }
+
+ DEFAULTS = {
+ 'user' => 'root',
+ 'puppetpath' => '/etc/puppet',
+ 'puppetvardir' => '/var/lib/puppet',
+ 'puppetbin' => '/usr/bin/puppet',
+ 'puppetbindir' => '/usr/bin'
+ }
+
+ def initialize(name, overrides, defaults)
+ super(name, overrides, defaults)
+
+ @defaults = defaults.merge(TestConfig.puppet_enterprise_version ? PE_DEFAULTS : DEFAULTS)
+ end
+ end
+end
View
@@ -0,0 +1,15 @@
+module Unix::Exec
+ include CommandFactory
+
+ def echo(msg, abs=true)
+ (abs ? '/bin/echo' : 'echo') + " #{msg}"
+ end
+
+ def touch(file, abs=true)
+ (abs ? '/bin/touch' : 'touch') + " #{file}"
+ end
+
+ def path
+ '/bin:/usr/bin'
+ end
+end
View
@@ -0,0 +1,15 @@
+module Unix::File
+ include CommandFactory
+
+ def tmpfile(name)
+ execute("mktemp -t #{name}.XXXXXX")
+ end
+
+ def tmpdir(name)
+ execute("mktemp -td #{name}.XXXXXX")
+ end
+
+ def path_split(paths)
+ paths.split(':')
+ end
+end
View
@@ -0,0 +1,32 @@
+module Unix::Group
+ include CommandFactory
+
+ def group_list(&block)
+ execute("getent group") do |result|
+ groups = []
+ result.stdout.each_line do |line|
+ groups << (line.match(/^([^:]+)/) or next)[1]
+ end
+
+ yield result if block_given?
+
+ groups
+ end
+ end
+
+ def group_get(name, &block)
+ execute("getent group #{name}") do |result|
+ fail_test "failed to get group #{name}" unless result.stdout =~ /^#{name}:.*:[0-9]+:/
+
+ yield result if block_given?
+ end
+ end
+
+ def group_present(name, &block)
+ execute("if ! getent group #{name}; then groupadd #{name}; fi", {}, &block)
+ end
+
+ def group_absent(name, &block)
+ execute("if getent group #{name}; then groupdel #{name}; fi", {}, &block)
+ end
+end
View
@@ -0,0 +1,32 @@
+module Unix::User
+ include CommandFactory
+
+ def user_list(&block)
+ execute("getent passwd") do |result|
+ users = []
+ result.stdout.each_line do |line|
+ users << (line.match( /^([^:]+)/) or next)[1]
+ end
+
+ yield result if block_given?
+
+ users
+ end
+ end
+
+ def user_get(name, &block)
+ execute("getent passwd #{name}") do |result|
+ fail_test "failed to get user #{name}" unless result.stdout =~ /^#{name}:/
+
+ yield result if block_given?
+ end
+ end
+
+ def user_present(name, &block)
+ execute("if ! getent passwd #{name}; then useradd #{name}; fi", {}, &block)
+ end
+
+ def user_absent(name, &block)
+ execute("if getent passwd #{name}; then userdel #{name}; fi", {}, &block)
+ end
+end
View
@@ -0,0 +1,29 @@
+require 'lib/host'
+require 'lib/command_factory'
+
+module Windows
+ class Host < Host
+ require 'lib/host/windows/user'
+ require 'lib/host/windows/group'
+ require 'lib/host/windows/file'
+ require 'lib/host/windows/exec'
+
+ include Windows::User
+ include Windows::Group
+ include Windows::File
+ include Windows::Exec
+
+ DEFAULTS = {
+ 'user' => 'Administrator',
+ 'group' => 'Administrators',
+ 'puppetpath' => '"`cygpath -F 35`/PuppetLabs/puppet/etc"',
+ 'puppetvardir' => '"`cygpath -F 35`/PuppetLabs/puppet/var"'
+ }
+
+ def initialize(name, overrides, defaults)
+ super(name, overrides, defaults)
+
+ @defaults = defaults.merge(DEFAULTS)
+ end
+ end
+end
View
@@ -0,0 +1,18 @@
+module Windows::Exec
+ include CommandFactory
+
+ ABS_CMD = 'c:\\\\windows\\\\system32\\\\cmd.exe'
+ CMD = 'cmd.exe'
+
+ def echo(msg, abs=true)
+ (abs ? ABS_CMD : CMD) + " /c echo #{msg}"
+ end
+
+ def touch(file, abs=true)
+ (abs ? ABS_CMD : CMD) + " /c echo. 2> #{file}"
+ end
+
+ def path
+ 'c:/windows/system32;c:/windows'
+ end
+end
View
@@ -0,0 +1,15 @@
+module Windows::File
+ include CommandFactory
+
+ def tmpfile(name)
+ execute("cygpath -m $(mktemp -t #{name}.XXXXXX)")
+ end
+
+ def tmpdir(name)
+ execute("cygpath -m $(mktemp -td #{name}.XXXXXX)")
+ end
+
+ def path_split(paths)
+ paths.split(';')
+ end
+end
Oops, something went wrong.

0 comments on commit f9cb52b

Please sign in to comment.