Class: RSpecSystem::NodeSet::Base Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/rspec-system/node_set/base.rb

Overview

This class is abstract.

Subclass and override methods to create a new NodeSet variant.

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.

Direct Known Subclasses

Openstack, VagrantBase, Vsphere

Instance Attribute Summary collapse

Abstract Methods collapse

Common Methods collapse

Constructor Details

#initialize(setname, config, custom_prefabs_path, options) ⇒ Base

This method is abstract.

override for providing global storage and setup-level code

Create new NodeSet, populating necessary data structures.



18
19
20
21
22
23
24
25
26
27
28
# 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

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



7
8
9
# File 'lib/rspec-system/node_set/base.rb', line 7

def config
  @config
end

#custom_prefabs_pathObject (readonly)

Returns the value of attribute custom_prefabs_path.



9
10
11
# File 'lib/rspec-system/node_set/base.rb', line 9

def custom_prefabs_path
  @custom_prefabs_path
end

#destroyObject (readonly)

Returns the value of attribute destroy.



11
12
13
# File 'lib/rspec-system/node_set/base.rb', line 11

def destroy
  @destroy
end

#nodesObject (readonly)

Returns the value of attribute nodes.



10
11
12
# File 'lib/rspec-system/node_set/base.rb', line 10

def nodes
  @nodes
end

#setnameObject (readonly)

Returns the value of attribute setname.



8
9
10
# File 'lib/rspec-system/node_set/base.rb', line 8

def setname
  @setname
end

Instance Method Details

#configurevoid

This method is abstract.

Override this method and provide your own configure code

This method returns an undefined value.

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



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# 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

#connectvoid

This method is abstract.

Override this method and provide your own connect code

This method returns an undefined value.

Connect nodes



51
52
53
# File 'lib/rspec-system/node_set/base.rb', line 51

def connect
  raise RuntimeError "Unimplemented method #connect"
end

#default_nodeRSpecSystem::Node

Return default node

Returns:



169
170
171
172
173
174
175
176
177
178
179
180
181
# 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

#launchvoid

This method is abstract.

Override this method and provide your own launch code

This method returns an undefined value.

Launch nodes



43
44
45
# File 'lib/rspec-system/node_set/base.rb', line 43

def launch
  raise RuntimeError "Unimplemented method #launch"
end

#provider_typeObject

Return environment type



162
163
164
# File 'lib/rspec-system/node_set/base.rb', line 162

def provider_type
  self.class::PROVIDER_TYPE
end

#randmacString

Return a random mac address

Returns:

  • (String)

    a random mac address



286
287
288
# File 'lib/rspec-system/node_set/base.rb', line 286

def randmac
  "080027" + (1..3).map{"%0.2X"%rand(256)}.join
end

#random_string(length = 50) ⇒ String

Return a random string of chars, used for temp dir creation

Returns:

  • (String)

    string of 50 random characters A-Z and a-z



186
187
188
189
# 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

#rcp(opts) ⇒ Boolean

This method is abstract.

Override this method providing your own file transfer code

Copy a file to the host in the NodeSet.

Parameters:

  • opts (Hash)

    options

Options Hash (opts):

  • :d (RSpecHelper::Node)

    destination node

  • :sp (String)

    source path

  • :dp (String)

    destination path

Returns:

  • (Boolean)

    returns true if command succeeded, false otherwise



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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(opts) ⇒ Hash

This method is abstract.

Override this method providing your own shell running code

Run a command on a host in the NodeSet.

Parameters:

  • opts (Hash)

    options hash containing :n (node) and :c (command)

Returns:

  • (Hash)

    a hash containing :stderr, :stdout and :exit_code



125
126
127
128
129
130
131
# 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

#setupvoid

This method returns an undefined value.

Setup the NodeSet by starting all nodes.



33
34
35
36
37
# File 'lib/rspec-system/node_set/base.rb', line 33

def setup
  launch
  connect
  configure
end

#ssh_connect(opts = {}) ⇒ Net::SSH::Connection::Session

Connect via SSH in a resilient way

Parameters:

  • opts (Hash) (defaults to: {})

Options Hash (opts):

  • :host (String)

    Host to connect to

  • :user (String)

    User to connect as

  • :net_ssh_options (Hash)

    Options hash as used by ‘Net::SSH.start`

Returns:

  • (Net::SSH::Connection::Session)


208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# 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

#ssh_exec!(ssh, command) ⇒ Hash

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.

Parameters:

  • ssh (Net::SSH::Connection::Session)

    an active ssh session

  • command (String)

    command to execute

Returns:

  • (Hash)

    a hash of results



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# 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

#teardownvoid

This method is abstract.

Override this method and provide your own node teardown code

This method returns an undefined value.

Shutdown the NodeSet by shutting down or pausing all nodes.



116
117
118
# File 'lib/rspec-system/node_set/base.rb', line 116

def teardown
  raise RuntimeError "Unimplemented method #teardown"
end

#tmppathString

TODO:

Very Linux dependant, probably need to consider OS X and Windows at least.

Generates a random string for use in remote transfers.

Returns:

  • (String)

    a random path



196
197
198
# File 'lib/rspec-system/node_set/base.rb', line 196

def tmppath
  '/tmp/' + random_string
end