Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
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...
commit f9cb52b2463c255a417c78ff7820ae09911b4ec9 2 parents e36f0d2 + 64e3330
@djm68 djm68 authored
View
21 lib/command.rb
@@ -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
19 lib/command_factory.rb
@@ -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
114 lib/host.rb
@@ -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
37 lib/host/unix.rb
@@ -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
15 lib/host/unix/exec.rb
@@ -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
15 lib/host/unix/file.rb
@@ -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
32 lib/host/unix/group.rb
@@ -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
32 lib/host/unix/user.rb
@@ -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
29 lib/host/windows.rb
@@ -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
18 lib/host/windows/exec.rb
@@ -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
15 lib/host/windows/file.rb
@@ -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
View
32 lib/host/windows/group.rb
@@ -0,0 +1,32 @@
+module Windows::Group
+ include CommandFactory
+
+ def group_list(&block)
+ execute('wmic group where localaccount="true" get name /format:value') do |result|
+ groups = []
+ result.stdout.each_line do |line|
+ groups << (line.match(/^Name=([\w ]+)/) or next)[1]
+ end
+
+ yield result if block_given?
+
+ groups
+ end
+ end
+
+ def group_get(name, &block)
+ execute("net localgroup \"#{name}\"") do |result|
+ fail_test "failed to get group #{name}" unless result.stdout =~ /^Alias name\s+#{name}/
+
+ yield result if block_given?
+ end
+ end
+
+ def group_present(name, &block)
+ execute("net localgroup /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block)
+ end
+
+ def group_absent(name, &block)
+ execute("net localgroup /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block)
+ end
+end
View
32 lib/host/windows/user.rb
@@ -0,0 +1,32 @@
+module Windows::User
+ include CommandFactory
+
+ def user_list(&block)
+ execute('wmic useraccount where localaccount="true" get name /format:value') do |result|
+ users = []
+ result.stdout.each_line do |line|
+ users << (line.match(/^Name=([\w ]+)/) or next)[1]
+ end
+
+ yield result if block_given?
+
+ users
+ end
+ end
+
+ def user_get(name, &block)
+ execute("net user \"#{name}\"") do |result|
+ fail_test "failed to get user #{name}" unless result.stdout =~ /^User name\s+#{name}/
+
+ yield result if block_given?
+ end
+ end
+
+ def user_present(name, &block)
+ execute("net user /add \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block)
+ end
+
+ def user_absent(name, &block)
+ execute("net user /delete \"#{name}\"", {:acceptable_exit_codes => [0,2]}, &block)
+ end
+end
View
15 lib/test_case.rb
@@ -1,5 +1,5 @@
class TestCase
- require 'lib/test_case/host'
+ require 'lib/host'
require 'tempfile'
require 'benchmark'
require 'stringio'
@@ -90,8 +90,6 @@ def test_name(test_name,&block)
#
attr_reader :result
def on(host, command, options={}, &block)
- options[:acceptable_exit_codes] ||= [0]
- options[:failing_exit_codes] ||= [1]
if command.is_a? String
command = Command.new(command)
end
@@ -100,17 +98,6 @@ def on(host, command, options={}, &block)
else
@result = command.exec(host, options)
- unless options[:silent] then
- result.log
- if options[:acceptable_exit_codes].include?(exit_code)
- # cool.
- elsif options[:failing_exit_codes].include?(exit_code)
- assert( false, "Host '#{host} exited with #{exit_code} running: #{command.cmd_line('')}" )
- else
- raise "Host '#{host}' exited with #{exit_code} running: #{command.cmd_line('')}"
- end
- end
-
# Also, let additional checking be performed by the caller.
yield if block_given?
View
148 lib/test_case/host.rb
@@ -1,148 +0,0 @@
-class TestCase
- class Host
- def self.create(name, overrides, defaults)
- case overrides['platform']
- when /windows/;
- WindowsHost.new(name, overrides, defaults)
- else
- UnixHost.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'] || "root", 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
-
- class UnixHost < Host
- PE_DEFAULTS = {
- 'puppetpath' => '/etc/puppetlabs/puppet',
- 'puppetbin' => '/usr/local/bin/puppet',
- 'puppetbindir' => '/opt/puppet/bin'
- }
-
- DEFAULTS = {
- '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
-
- class WindowsHost < Host
- DEFAULTS = {
- 'user' => 'Administrator',
- '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
4 lib/test_suite.rb
@@ -43,10 +43,12 @@ def run
@test_files.each do |test_file|
Log.notify
Log.notify "Begin #{test_file}"
+ start = Time.now
test_case = TestCase.new(@hosts, config, options, test_file).run_test
+ duration = Time.now - start
@test_cases << test_case
- msg = "#{test_file} #{test_case.test_status == :skip ? 'skipp' : test_case.test_status}ed"
+ msg = "#{test_file} #{test_case.test_status == :skip ? 'skipp' : test_case.test_status}ed in %.2f seconds" % duration.to_f
case test_case.test_status
when :pass
Log.success msg
View
4 systest.rb
@@ -9,7 +9,7 @@
require 'systemu'
require 'test/unit'
require 'yaml'
-require 'lib/test_case/host'
+require 'lib/host'
Test::Unit.run = true
Dir.glob(File.dirname(__FILE__) + '/lib/*.rb') {|file| require file}
@@ -52,7 +52,7 @@
end
# Generate hosts
-hosts = config['HOSTS'].collect { |name,overrides| TestCase::Host.create(name,overrides,config['CONFIG']) }
+hosts = config['HOSTS'].collect { |name,overrides| Host.create(name,overrides,config['CONFIG']) }
begin
# Run the harness for install
TestSuite.new('setup', hosts, setup_options, config, TRUE).run_and_exit_on_failure
Please sign in to comment.
Something went wrong with that request. Please try again.