class RSpecSystem::NodeSet::Base
Base
class for a NodeSet
driver. If you want to create a new driver, you should sub-class this and override all the methods below.
@abstract Subclass and override methods to create a new NodeSet
variant.
Attributes
Public Class Methods
Create new NodeSet
, populating necessary data structures.
@abstract override for providing global storage and setup-level code
# File lib/rspec-system/node_set/base.rb, line 18 def initialize(setname, config, custom_prefabs_path, options) @setname = setname @config = config @custom_prefabs_path = custom_prefabs_path @destroy = options[:destroy] @nodes = {} config['nodes'].each do |k,v| @nodes[k] = RSpecSystem::Node.node_from_yaml(self, k, v, custom_prefabs_path) end end
Public Instance Methods
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
# File lib/rspec-system/node_set/base.rb, line 66 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
Connect nodes
@return [void] @abstract Override this method and provide your own connect code
# File lib/rspec-system/node_set/base.rb, line 51 def connect raise RuntimeError "Unimplemented method #connect" end
Return default node
@return [RSpecSystem::Node] default node for this nodeset
# File lib/rspec-system/node_set/base.rb, line 169 def default_node dn = config['default_node'] if dn.nil? if nodes.length == 1 dn = nodes.first[1] return dn else raise "No default node" end else return nodes[dn] end end
Launch nodes
@return [void] @abstract Override this method and provide your own launch code
# File lib/rspec-system/node_set/base.rb, line 43 def launch raise RuntimeError "Unimplemented method #launch" end
Return environment type
# File lib/rspec-system/node_set/base.rb, line 162 def provider_type self.class::PROVIDER_TYPE end
Return a random mac address
@return [String] a random mac address
# File lib/rspec-system/node_set/base.rb, line 286 def randmac "080027" + (1..3).map{"%0.2X"%rand(256)}.join end
Return a random string of chars, used for temp dir creation
@return [String] string of 50 random characters A-Z and a-z
# File lib/rspec-system/node_set/base.rb, line 186 def random_string(length = 50) o = [('a'..'z'),('A'..'Z')].map{|i| i.to_a}.flatten (0...length).map{ o[rand(o.length)] }.join end
Copy a file to the host in the NodeSet
.
@param opts [Hash] options @option opts [RSpecHelper::Node] :d destination node @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
# File lib/rspec-system/node_set/base.rb, line 141 def rcp(opts) 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
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
# File lib/rspec-system/node_set/base.rb, line 125 def run(opts) dest = opts[:n].name cmd = opts[:c] ssh = RSpec.configuration.rs_storage[:nodes][dest][:ssh] ssh_exec!(ssh, cmd) end
Setup the NodeSet
by starting all nodes.
@return [void]
# File lib/rspec-system/node_set/base.rb, line 33 def setup launch connect configure 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
# File lib/rspec-system/node_set/base.rb, line 208 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, Net::SSH::Exception, SocketError => 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 signal as well. This method is blocking.
@param ssh [Net::SSH::Connection::Session] an active ssh session @param command [String] command to execute @return [Hash] a hash of results
# File lib/rspec-system/node_set/base.rb, line 241 def ssh_exec!(ssh, command) r = { :stdout => '', :stderr => '', :exit_code => nil, :exit_signal => nil, } ssh.open_channel do |channel| channel.exec(command) do |ch, success| unless success abort "FAILED: couldn't execute command (ssh.channel.exec)" end channel.on_data do |ch,data| d = data output << d r[:stdout]+=d end channel.on_extended_data do |ch,type,data| d = data output << d r[:stderr]+=d end channel.on_request("exit-status") do |ch,data| c = data.read_long output << bold("Exit code:") << " #{c}\n" r[:exit_code] = c end channel.on_request("exit-signal") do |ch, data| s = data.read_string output << bold("Exit signal:") << " #{s}\n" r[:exit_signal] = s end end end ssh.loop r end
Shutdown the NodeSet
by shutting down or pausing all nodes.
@return [void] @abstract Override this method and provide your own node teardown code
# File lib/rspec-system/node_set/base.rb, line 116 def teardown raise RuntimeError "Unimplemented method #teardown" end
Generates a random string for use in remote transfers.
@return [String] a random path @todo Very Linux dependant, probably need to consider OS X and Windows at
least.
# File lib/rspec-system/node_set/base.rb, line 196 def tmppath '/tmp/' + random_string end