rspec-system 2.5.1 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,13 +5,22 @@ require 'net/scp'
5
5
  require 'rspec-system/node_set/base'
6
6
 
7
7
  module RSpecSystem
8
- # A NodeSet implementation for Vagrant.
9
- class NodeSet::Vagrant < RSpecSystem::NodeSet::Base
8
+ # An abstract NodeSet implementation for Vagrant.
9
+ class NodeSet::VagrantBase < RSpecSystem::NodeSet::Base
10
10
  include RSpecSystem::Log
11
11
  include RSpecSystem::Util
12
12
 
13
- ENV_TYPE = 'vagrant'
14
- VALID_VM_OPTIONS = ['ip']
13
+ # @!group Abstract Methods
14
+
15
+ # The vagrant specific provider name
16
+ #
17
+ # @return [String] name of the provider as used by `vagrant --provider`
18
+ # @abstract override to return the name of the vagrant provider
19
+ def vagrant_provider_name
20
+ raise RuntimeError, "Unimplemented method #vagrant_provider_name"
21
+ end
22
+
23
+ # @!group Common Methods
15
24
 
16
25
  # Creates a new instance of RSpecSystem::NodeSet::Vagrant
17
26
  #
@@ -21,30 +30,49 @@ module RSpecSystem
21
30
  # @param options [Hash] options Hash
22
31
  def initialize(setname, config, custom_prefabs_path, options)
23
32
  super
24
- @vagrant_path = File.expand_path(File.join(RSpec.configuration.system_tmp, 'vagrant_projects', setname))
33
+ @vagrant_path = File.expand_path(File.join(RSpec.configuration.rs_tmp, 'vagrant_projects', setname))
25
34
 
26
- RSpec.configuration.rspec_storage[:nodes] ||= {}
35
+ RSpec.configuration.rs_storage[:nodes] ||= {}
27
36
  end
28
37
 
29
- # Setup the NodeSet by starting all nodes.
38
+ # Launch the nodes
30
39
  #
31
40
  # @return [void]
32
- def setup
41
+ def launch
33
42
  create_vagrantfile()
34
43
 
35
44
  teardown()
36
45
 
37
- output << bold(color("localhost$", :green)) << " vagrant up\n"
38
- vagrant("up")
46
+ nodes.each do |k,v|
47
+ RSpec.configuration.rs_storage[:nodes][k] ||= {}
48
+ output << bold(color("localhost$", :green)) << " vagrant up #{k}\n"
49
+ vagrant("up #{k} --provider=#{vagrant_provider_name}")
50
+ end
51
+
52
+ nil
53
+ end
39
54
 
40
- # Establish ssh connectivity
55
+ # Connect to the nodes
56
+ #
57
+ # @return [void]
58
+ def connect
41
59
  nodes.each do |k,v|
42
- output << bold(color("localhost$", :green)) << " ssh #{k}\n"
43
- chan = Net::SSH.start(k, 'vagrant', :config => ssh_config)
60
+ RSpec.configuration.rs_storage[:nodes][k] ||= {}
61
+
62
+ chan = ssh_connect(:host => k, :user => 'vagrant', :net_ssh_options => {
63
+ :config => ssh_config
64
+ })
44
65
 
45
- RSpec.configuration.rspec_storage[:nodes][k] = {
46
- :ssh => chan,
47
- }
66
+ # Copy the authorized keys from vagrant user to root then reconnect
67
+ cmd = 'mkdir /root/.ssh ; cp /home/vagrant/.ssh/authorized_keys /root/.ssh'
68
+
69
+ output << bold(color("#{k}$ ", :green)) << cmd << "\n"
70
+ ssh_exec!(chan, "cd /tmp && sudo sh -c #{shellescape(cmd)}")
71
+
72
+ chan = ssh_connect(:host => k, :user => 'root', :net_ssh_options => {
73
+ :config => ssh_config
74
+ })
75
+ RSpec.configuration.rs_storage[:nodes][k][:ssh] = chan
48
76
  end
49
77
 
50
78
  nil
@@ -55,7 +83,7 @@ module RSpecSystem
55
83
  # @return [void]
56
84
  def teardown
57
85
  nodes.each do |k,v|
58
- storage = RSpec.configuration.rspec_storage[:nodes][k]
86
+ storage = RSpec.configuration.rs_storage[:nodes][k]
59
87
 
60
88
  next if storage.nil?
61
89
 
@@ -67,49 +95,8 @@ module RSpecSystem
67
95
  output << bold(color("localhost$", :green)) << " vagrant destroy --force\n"
68
96
  vagrant("destroy --force")
69
97
  end
70
- nil
71
- end
72
98
 
73
- # Run a command on a host in the NodeSet.
74
- #
75
- # @param opts [Hash] options
76
- # @return [Hash] a hash containing :exit_code, :stdout and :stderr
77
- def run(opts)
78
- dest = opts[:n].name
79
- cmd = opts[:c]
80
-
81
- ssh = RSpec.configuration.rspec_storage[:nodes][dest][:ssh]
82
- ssh_exec!(ssh, "cd /tmp && sudo sh -c #{shellescape(cmd)}")
83
- end
84
-
85
- # Transfer files to a host in the NodeSet.
86
- #
87
- # @param opts [Hash] options
88
- # @return [Boolean] returns true if command succeeded, false otherwise
89
- # @todo This is damn ugly, because we ssh in as vagrant, we copy to a temp
90
- # path then move it later. Its slow and brittle and we need a better
91
- # solution. Its also very Linux-centrix in its use of temp dirs.
92
- def rcp(opts)
93
- dest = opts[:d].name
94
- source = opts[:sp]
95
- dest_path = opts[:dp]
96
-
97
- # Grab a remote path for temp transfer
98
- tmpdest = tmppath
99
-
100
- # Do the copy and print out results for debugging
101
- cmd = "scp -r '#{source}' #{dest}:#{tmpdest}"
102
- output << bold(color("localhost$", :green)) << " #{cmd}\n"
103
- ssh = RSpec.configuration.rspec_storage[:nodes][dest][:ssh]
104
- ssh.scp.upload! source.to_s, tmpdest.to_s, :recursive => true
105
-
106
- # Now we move the file into their final destination
107
- result = shell(:n => opts[:d], :c => "mv #{tmpdest} #{dest_path}")
108
- if result[:exit_code] == 0
109
- return true
110
- else
111
- return false
112
- end
99
+ nil
113
100
  end
114
101
 
115
102
  # Create the Vagrantfile for the NodeSet.
@@ -121,7 +108,7 @@ module RSpecSystem
121
108
  File.open(File.expand_path(File.join(@vagrant_path, "Vagrantfile")), 'w') do |f|
122
109
  f.write('Vagrant.configure("2") do |c|' + "\n")
123
110
  nodes.each do |k,v|
124
- ps = v.provider_specifics['vagrant']
111
+ ps = v.provider_specifics[provider_type]
125
112
  default_options = { 'mac' => randmac }
126
113
  options = default_options.merge(v.options || {})
127
114
 
@@ -130,8 +117,8 @@ module RSpecSystem
130
117
  node_config << " v.vm.box = '#{ps['box']}'\n"
131
118
  node_config << " v.vm.box_url = '#{ps['box_url']}'\n" unless ps['box_url'].nil?
132
119
  node_config << customize_vm(k,options)
133
- node_config << " v.vm.provider 'virtualbox' do |vbox|\n"
134
- node_config << customize_virtualbox(k,options)
120
+ node_config << " v.vm.provider '#{vagrant_provider_name}' do |prov, override|\n"
121
+ node_config << customize_provider(k,options)
135
122
  node_config << " end\n"
136
123
  node_config << " end\n"
137
124
 
@@ -142,26 +129,15 @@ module RSpecSystem
142
129
  nil
143
130
  end
144
131
 
145
- # Adds virtualbox customization to the Vagrantfile
146
- #
132
+ # Add provider specific customization to the Vagrantfile
133
+ #
147
134
  # @api private
148
135
  # @param name [String] name of the node
149
136
  # @param options [Hash] customization options
150
- # @return [String] a series of vbox.customize lines
151
- def customize_virtualbox(name,options)
152
- custom_config = ""
153
- options.each_pair do |key,value|
154
- next if VALID_VM_OPTIONS.include?(key)
155
- case key
156
- when 'cpus','memory'
157
- custom_config << " vbox.customize ['modifyvm', :id, '--#{key}','#{value}']\n"
158
- when 'mac'
159
- custom_config << " vbox.customize ['modifyvm', :id, '--macaddress1','#{value}']\n"
160
- else
161
- log.warn("Skipped invalid custom option for node #{name}: #{key}=#{value}")
162
- end
163
- end
164
- custom_config
137
+ # @return [String] a series of prov.customize lines
138
+ # @abstract Overridet ot provide your own customizations
139
+ def customize_provider(name,options)
140
+ ''
165
141
  end
166
142
 
167
143
  # Adds VM customization to the Vagrantfile
@@ -207,9 +183,6 @@ module RSpecSystem
207
183
  #
208
184
  # @api private
209
185
  # @param args [String] args to vagrant
210
- # @todo This seems a little too specific these days, might want to
211
- # generalize. It doesn't use systemu, because we want to see the output
212
- # immediately, but still - maybe we can make systemu do that.
213
186
  def vagrant(args)
214
187
  Dir.chdir(@vagrant_path) do
215
188
  system("vagrant #{args}")
@@ -217,5 +190,13 @@ module RSpecSystem
217
190
  nil
218
191
  end
219
192
 
193
+ # Returns a list of options that apply to all types of vagrant providers
194
+ #
195
+ # @return [Array<String>] Array of options
196
+ # @api private
197
+ def global_vagrant_options
198
+ ['ip']
199
+ end
200
+
220
201
  end
221
202
  end
@@ -0,0 +1,41 @@
1
+ require 'fileutils'
2
+ require 'systemu'
3
+ require 'net/ssh'
4
+ require 'net/scp'
5
+ require 'rspec-system/node_set/vagrant_base'
6
+
7
+ module RSpecSystem
8
+ # A NodeSet implementation for Vagrant.
9
+ class NodeSet::VagrantVirtualbox < NodeSet::VagrantBase
10
+ PROVIDER_TYPE = 'vagrant_virtualbox'
11
+
12
+ # Name of provider
13
+ #
14
+ # @return [String] name of the provider as used by `vagrant --provider`
15
+ def vagrant_provider_name
16
+ 'virtualbox'
17
+ end
18
+
19
+ # Adds virtualbox customization to the Vagrantfile
20
+ #
21
+ # @api private
22
+ # @param name [String] name of the node
23
+ # @param options [Hash] customization options
24
+ # @return [String] a series of vbox.customize lines
25
+ def customize_provider(name,options)
26
+ custom_config = ""
27
+ options.each_pair do |key,value|
28
+ next if global_vagrant_options.include?(key)
29
+ case key
30
+ when 'cpus','memory'
31
+ custom_config << " prov.customize ['modifyvm', :id, '--#{key}','#{value}']\n"
32
+ when 'mac'
33
+ custom_config << " prov.customize ['modifyvm', :id, '--macaddress1','#{value}']\n"
34
+ else
35
+ log.warn("Skipped invalid custom option for node #{name}: #{key}=#{value}")
36
+ end
37
+ end
38
+ custom_config
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,43 @@
1
+ require 'fileutils'
2
+ require 'systemu'
3
+ require 'net/ssh'
4
+ require 'net/scp'
5
+ require 'rspec-system/node_set/vagrant_base'
6
+
7
+ module RSpecSystem
8
+ # A NodeSet implementation for Vagrant using the vmware_fusion provider
9
+ class NodeSet::VagrantVmwareFusion < NodeSet::VagrantBase
10
+ PROVIDER_TYPE = 'vagrant_vmware_fusion'
11
+
12
+ # Name of provider
13
+ #
14
+ # @return [String] name of the provider as used by `vagrant --provider`
15
+ def vagrant_provider_name
16
+ 'vmware_fusion'
17
+ end
18
+
19
+ # Adds virtualbox customization to the Vagrantfile
20
+ #
21
+ # @api private
22
+ # @param name [String] name of the node
23
+ # @param options [Hash] customization options
24
+ # @return [String] a series of vbox.customize lines
25
+ def customize_provider(name,options)
26
+ custom_config = ""
27
+ options.each_pair do |key,value|
28
+ next if global_vagrant_options.include?(key)
29
+ case key
30
+ when 'cpus'
31
+ custom_config << " prov.vmx['numvcpus'] = '#{value}'\n"
32
+ when 'memory'
33
+ custom_config << " prov.vmx['memsize'] = '#{value}'\n"
34
+ when 'mac'
35
+ custom_config << " prov.vmx['ethernet0.generatedAddress'] = '#{value}'\n"
36
+ else
37
+ log.warn("Skipped invalid custom option for node #{name}: #{key}=#{value}")
38
+ end
39
+ end
40
+ custom_config
41
+ end
42
+ end
43
+ end
@@ -10,7 +10,7 @@ module RSpecSystem
10
10
  class NodeSet::Vsphere < RSpecSystem::NodeSet::Base
11
11
  include RSpecSystem::Log
12
12
 
13
- ENV_TYPE = 'vsphere'
13
+ PROVIDER_TYPE = 'vsphere'
14
14
 
15
15
  attr_reader :vmconf
16
16
 
@@ -26,8 +26,7 @@ module RSpecSystem
26
26
  # Valid supported ENV variables
27
27
  options = [:host, :user, :pass, :dest_dir, :template_dir, :rpool,
28
28
  :cluster, :ssh_keys, :datacenter, :node_timeout, :node_tries,
29
- :node_sleep, :ssh_timeout, :ssh_tries, :ssh_sleep, :connect_timeout,
30
- :connect_tries]
29
+ :node_sleep, :connect_timeout, :connect_tries]
31
30
 
32
31
  # Devise defaults, use fog configuration from file system if it exists
33
32
  defaults = load_fog_config()
@@ -35,9 +34,6 @@ module RSpecSystem
35
34
  :node_timeout => 1200,
36
35
  :node_tries => 10,
37
36
  :node_sleep => 30 + rand(60),
38
- :ssh_timeout => 60,
39
- :ssh_tries => 10,
40
- :ssh_sleep => 4,
41
37
  :connect_timeout => 60,
42
38
  :connect_tries => 10,
43
39
  })
@@ -45,8 +41,8 @@ module RSpecSystem
45
41
  # Traverse the ENV variables and load them into our config automatically
46
42
  @vmconf = defaults
47
43
  ENV.each do |k,v|
48
- next unless k =~/^RSPEC_VSPHERE_/
49
- var = k.sub(/^RSPEC_VSPHERE_/, '').downcase.to_sym
44
+ next unless k =~/^RS(PEC)?_VSPHERE_/
45
+ var = k.sub(/^RS(PEC)?_VSPHERE_/, '').downcase.to_sym
50
46
  unless options.include?(var)
51
47
  log.info("Ignoring unknown environment variable #{k}")
52
48
  next
@@ -55,7 +51,7 @@ module RSpecSystem
55
51
  end
56
52
 
57
53
  # Initialize node storage if not already
58
- RSpec.configuration.rspec_storage[:nodes] ||= {}
54
+ RSpec.configuration.rs_storage[:nodes] ||= {}
59
55
  end
60
56
 
61
57
  # Retrieves fog configuration if it exists
@@ -82,7 +78,7 @@ module RSpecSystem
82
78
  # The connection handling automatically retries upon failure.
83
79
  #
84
80
  # @api private
85
- def with_connection(&block)
81
+ def with_vsphere_connection(&block)
86
82
  vim = nil
87
83
  dc = nil
88
84
 
@@ -122,11 +118,11 @@ module RSpecSystem
122
118
 
123
119
  # @!group NodeSet Methods
124
120
 
125
- # Setup the NodeSet by starting all nodes.
121
+ # Launch the nodes
126
122
  #
127
123
  # @return [void]
128
- def setup
129
- with_connection do |dc|
124
+ def launch
125
+ with_vsphere_connection do |dc|
130
126
  # Traverse folders to find target folder for new vm's and template
131
127
  # folders. Automatically create the destination folder if it doesn't
132
128
  # exist.
@@ -148,10 +144,7 @@ module RSpecSystem
148
144
 
149
145
  log.info "Launching VSphere instances one by one"
150
146
  nodes.each do |k,v|
151
- #####################
152
- # Node launching step
153
- #####################
154
- RSpec.configuration.rspec_storage[:nodes][k] ||= {}
147
+ RSpec.configuration.rs_storage[:nodes][k] ||= {}
155
148
 
156
149
  # Obtain the template name to use
157
150
  ps = v.provider_specifics['vsphere']
@@ -165,7 +158,7 @@ module RSpecSystem
165
158
 
166
159
  # Create a random name for the new VM
167
160
  vm_name = "rspec-system-#{k}-#{random_string(10)}"
168
- RSpec.configuration.rspec_storage[:nodes][k][:vm] = vm_name
161
+ RSpec.configuration.rs_storage[:nodes][k][:vm] = vm_name
169
162
 
170
163
  log.info "Launching VSphere instance #{k} with template #{vmconf[:template_dir]}/#{template} as #{vmconf[:dest_dir]}/#{vm_name}"
171
164
 
@@ -201,7 +194,7 @@ module RSpecSystem
201
194
  time3 = Time.now
202
195
  log.info "#{k}> Time in seconds waiting for IP: #{time3 - time2}"
203
196
  end
204
- RSpec.configuration.rspec_storage[:nodes][k][:ipaddress] = ipaddress
197
+ RSpec.configuration.rs_storage[:nodes][k][:ipaddress] = ipaddress
205
198
  rescue Timeout::Error, SystemCallError => e
206
199
  tries += 1
207
200
  log.error("VM launch attempt #{tries} failed with: " + e.message)
@@ -227,47 +220,31 @@ module RSpecSystem
227
220
  raise e
228
221
  end
229
222
  end
223
+
230
224
  time2 = Time.now
231
225
  log.info "#{k}> Took #{time2 - start_time} seconds to boot instance"
226
+ end
227
+ end
232
228
 
233
- #####################
234
- # SSH Step
235
- #####################
236
- tries = 0
237
- begin
238
- timeout(vmconf[:ssh_timeout]) do
239
- output << bold(color("localhost$", :green)) << " ssh #{k}\n"
240
- chan = Net::SSH.start(ipaddress, 'root', {
241
- :keys => vmconf[:ssh_keys].split(":"),
242
- })
229
+ nil
230
+ end
243
231
 
244
- RSpec.configuration.rspec_storage[:nodes][k][:ssh] = chan
245
- end
246
- rescue Timeout::Error, SystemCallError => e
247
- tries += 1
248
- output << e.message << "\n"
249
- if tries < vmconf[:ssh_tries]
250
- log.info("Sleeping for #{vmconf[:ssh_sleep]} seconds then trying again ...")
251
- sleep vmconf[:ssh_sleep]
252
- retry
253
- else
254
- log.error("Inability to connect to host, already tried #{tries} times, throwing exception")
255
- raise e
256
- end
257
- end
258
- time3 = Time.now
259
- log.info "#{k}> Took #{time3 - start_time} seconds for instance to be ready"
260
-
261
- ######################
262
- # Do initial box setup
263
- ######################
264
- hosts = <<-EOS
265
- 127.0.0.1 localhost localhost.localdomain
266
- #{ipaddress} #{k}
267
- EOS
268
- shell(:n => k, :c => "echo '#{hosts}' > /etc/hosts")
269
- shell(:n => k, :c => "hostname #{k}")
270
- end
232
+ # Connect to the nodes
233
+ #
234
+ # @return [void]
235
+ def connect
236
+ nodes.each do |k,v|
237
+ rs_storage = RSpec.configuration.rs_storage[:nodes][k]
238
+ raise RuntimeError, "No internal storage for node #{k}" if rs_storage.nil?
239
+
240
+ ipaddress = rs_storage[:ipaddress]
241
+ raise RuntimeError, "No ipaddress provided from launch phase for node #{k}" if ipaddress.nil?
242
+
243
+ chan = ssh_connect(:host => k, :user => 'root', :net_ssh_options => {
244
+ :keys => vmconf[:ssh_keys].split(":"),
245
+ :host_name => ipaddress,
246
+ })
247
+ RSpec.configuration.rs_storage[:nodes][k][:ssh] = chan
271
248
  end
272
249
 
273
250
  nil
@@ -277,9 +254,9 @@ module RSpecSystem
277
254
  #
278
255
  # @return [void]
279
256
  def teardown
280
- with_connection do |dc|
257
+ with_vsphere_connection do |dc|
281
258
  nodes.each do |k,v|
282
- storage = RSpec.configuration.rspec_storage[:nodes][k]
259
+ storage = RSpec.configuration.rs_storage[:nodes][k]
283
260
 
284
261
  if storage.nil?
285
262
  log.info "No entry for node #{k}, no teardown necessary"
@@ -325,42 +302,5 @@ module RSpecSystem
325
302
  nil
326
303
  end
327
304
 
328
- # Run a command on a host in the NodeSet.
329
- #
330
- # @param opts [Hash] options
331
- # @return [Hash] a hash containing :exit_code, :stdout and :stderr
332
- def run(opts)
333
- dest = opts[:n].name
334
- cmd = opts[:c]
335
-
336
- ssh = RSpec.configuration.rspec_storage[:nodes][dest][:ssh]
337
- ssh_exec!(ssh, cmd)
338
- end
339
-
340
- # Transfer files to a host in the NodeSet.
341
- #
342
- # @param opts [Hash] options
343
- # @return [Boolean] returns true if command succeeded, false otherwise
344
- # @todo This is damn ugly, because we ssh in as vagrant, we copy to a temp
345
- # path then move it later. Its slow and brittle and we need a better
346
- # solution. Its also very Linux-centrix in its use of temp dirs.
347
- def rcp(opts)
348
- dest = opts[:d].name
349
- source = opts[:sp]
350
- dest_path = opts[:dp]
351
-
352
- # Do the copy and print out results for debugging
353
- ssh = RSpec.configuration.rspec_storage[:nodes][dest][:ssh]
354
-
355
- begin
356
- ssh.scp.upload! source.to_s, dest_path.to_s, :recursive => true
357
- rescue => e
358
- log.error("Error with scp of file #{source} to #{dest}:#{dest_path}")
359
- raise e
360
- end
361
-
362
- true
363
- end
364
-
365
305
  end
366
306
  end