lib/rspec-system/node_set/base.rb in rspec-system-2.5.1 vs lib/rspec-system/node_set/base.rb in rspec-system-2.6.0

- old
+ new

@@ -28,30 +28,108 @@ end # Setup the NodeSet by starting all nodes. # # @return [void] - # @abstract Override this method and provide your own node launching code def setup - raise "Unimplemented method #setup" + launch + connect + configure end + # Launch nodes + # + # @return [void] + # @abstract Override this method and provide your own launch code + def launch + raise RuntimeError "Unimplemented method #launch" + end + + # Connect nodes + # + # @return [void] + # @abstract Override this method and provide your own connect code + def connect + raise RuntimeError "Unimplemented method #connect" + end + + # Configure nodes + # + # This is the global configure method that sets up a node before tests are + # run, making sure any important preparation steps are executed. + # + # * fixup profile to stop using mesg to avoid extraneous noise + # * ntp synchronisation + # * hostname & hosts setup + # + # @return [void] + # @abstract Override this method and provide your own configure code + def configure + nodes.each do |k,v| + rs_storage = RSpec.configuration.rs_storage[:nodes][k] + + # Fixup profile to avoid noise + if v.facts['osfamily'] == 'Debian' + shell(:n => k, :c => "sed -i 's/^mesg n/# mesg n/' /root/.profile") + end + + # Setup ntp + if v.facts['osfamily'] == 'Debian' then + shell(:n => k, :c => 'apt-get install -y ntpdate') + elsif v.facts['osfamily'] == 'RedHat' then + if v.facts['lsbmajdistrelease'] == '5' then + shell(:n => k, :c => 'yum install -y ntp') + else + shell(:n => k, :c => 'yum install -y ntpdate') + end + end + shell(:n => k, :c => 'ntpdate -u pool.ntp.org') + + # Grab IP address for host, if we don't already have one + rs_storage[:ipaddress] ||= shell(:n => k, :c => "ip a|awk '/g/{print$2}' | cut -d/ -f1 | head -1").stdout.chomp + + # Configure local hostname and hosts file + shell(:n => k, :c => "hostname #{k}") + + if v.facts['osfamily'] == 'Debian' then + shell(:n => k, :c => "echo '#{k}' > /etc/hostname") + end + + hosts = <<-EOS +#{rs_storage[:ipaddress]} #{k} +127.0.0.1 #{k} localhost +::1 #{k} localhost + EOS + shell(:n => k, :c => "echo '#{hosts}' > /etc/hosts") + + # Display setup for diagnostics + shell(:n => k, :c => 'cat /etc/hosts') + shell(:n => k, :c => 'hostname') + shell(:n => k, :c => 'hostname -f') + end + nil + end + # Shutdown the NodeSet by shutting down or pausing all nodes. # # @return [void] # @abstract Override this method and provide your own node teardown code def teardown - raise "Unimplemented method #teardown" + raise RuntimeError "Unimplemented method #teardown" end # Run a command on a host in the NodeSet. # # @param opts [Hash] options hash containing :n (node) and :c (command) # @return [Hash] a hash containing :stderr, :stdout and :exit_code # @abstract Override this method providing your own shell running code def run(opts) - raise "Unimplemented method #run" + dest = opts[:n].name + cmd = opts[:c] + + ssh = RSpec.configuration.rs_storage[:nodes][dest][:ssh] + ssh_exec!(ssh, cmd) end # Copy a file to the host in the NodeSet. # # @param opts [Hash] options @@ -59,18 +137,32 @@ # @option opts [String] :sp source path # @option opts [String] :dp destination path # @return [Boolean] returns true if command succeeded, false otherwise # @abstract Override this method providing your own file transfer code def rcp(opts) - raise "Unimplemented method #rcp" + dest = opts[:d].name + source = opts[:sp] + dest_path = opts[:dp] + + # Do the copy and print out results for debugging + ssh = RSpec.configuration.rs_storage[:nodes][dest][:ssh] + + begin + ssh.scp.upload! source.to_s, dest_path.to_s, :recursive => true + rescue => e + log.error("Error with scp of file #{source} to #{dest}:#{dest_path}") + raise e + end + + true end # @!group Common Methods # Return environment type - def env_type - self.class::ENV_TYPE + def provider_type + self.class::PROVIDER_TYPE end # Return default node # # @return [RSpecSystem::Node] default node for this nodeset @@ -101,9 +193,42 @@ # @return [String] a random path # @todo Very Linux dependant, probably need to consider OS X and Windows at # least. def tmppath '/tmp/' + random_string + end + + # Connect via SSH in a resilient way + # + # @param [Hash] opts + # @option opts [String] :host Host to connect to + # @option opts [String] :user User to connect as + # @option opts [Hash] :net_ssh_options Options hash as used by `Net::SSH.start` + # @return [Net::SSH::Connection::Session] + # @api protected + def ssh_connect(opts = {}) + ssh_sleep = RSpec.configuration.rs_ssh_sleep + ssh_tries = RSpec.configuration.rs_ssh_tries + ssh_timeout = RSpec.configuration.rs_ssh_timeout + + tries = 0 + begin + timeout(ssh_timeout) do + output << bold(color("localhost$", :green)) << " ssh -l #{opts[:user]} #{opts[:host]}\n" + Net::SSH.start(opts[:host], opts[:user], opts[:net_ssh_options]) + end + rescue Timeout::Error, SystemCallError => e + tries += 1 + output << e.message << "\n" + if tries < ssh_tries + log.info("Sleeping for #{ssh_sleep} seconds then trying again ...") + sleep ssh_sleep + retry + else + log.error("Inability to connect to host, already tried #{tries} times, throwing exception") + raise e + end + end end # Execute command via SSH. # # A special version of exec! from Net::SSH that returns exit code and exit