beaker-vmpooler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ require 'yaml' unless defined?(YAML)
2
+ require 'beaker/hypervisor/vmpooler'
3
+
4
+ module Beaker
5
+ class Vcloud < Beaker::Hypervisor
6
+
7
+ def self.new(vcloud_hosts, options)
8
+ if options['pooling_api']
9
+ Beaker::Vmpooler.new(vcloud_hosts, options)
10
+ else
11
+ super
12
+ end
13
+ end
14
+
15
+ def initialize(vcloud_hosts, options)
16
+ @options = options
17
+ @logger = options[:logger]
18
+ @hosts = vcloud_hosts
19
+
20
+ raise 'You must specify a datastore for vCloud instances!' unless @options['datastore']
21
+ raise 'You must specify a folder for vCloud instances!' unless @options['folder']
22
+ raise 'You must specify a datacenter for vCloud instances!' unless @options['datacenter']
23
+ @vsphere_credentials = VsphereHelper.load_config(@options[:dot_fog])
24
+ end
25
+
26
+ def connect_to_vsphere
27
+ @logger.notify "Connecting to vSphere at #{@vsphere_credentials[:server]}" +
28
+ " with credentials for #{@vsphere_credentials[:user]}"
29
+
30
+ @vsphere_helper = VsphereHelper.new( @vsphere_credentials )
31
+ end
32
+
33
+ def wait_for_dns_resolution host, try, attempts
34
+ @logger.notify "Waiting for #{host['vmhostname']} DNS resolution"
35
+ begin
36
+ Socket.getaddrinfo(host['vmhostname'], nil)
37
+ rescue
38
+ if try <= attempts
39
+ sleep 5
40
+ try += 1
41
+
42
+ retry
43
+ else
44
+ raise "DNS resolution failed after #{@options[:timeout].to_i} seconds"
45
+ end
46
+ end
47
+ end
48
+
49
+ def booting_host host, try, attempts
50
+ @logger.notify "Booting #{host['vmhostname']} (#{host.name}) and waiting for it to register with vSphere"
51
+ until
52
+ @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.toolsRunningStatus == 'guestToolsRunning' and
53
+ @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress != nil
54
+ if try <= attempts
55
+ sleep 5
56
+ try += 1
57
+ else
58
+ raise "vSphere registration failed after #{@options[:timeout].to_i} seconds"
59
+ end
60
+ end
61
+ end
62
+
63
+ # Directly borrowed from openstack hypervisor
64
+ def enable_root(host)
65
+ if host['user'] != 'root'
66
+ copy_ssh_to_root(host, @options)
67
+ enable_root_login(host, @options)
68
+ host['user'] = 'root'
69
+ host.close
70
+ end
71
+ end
72
+
73
+ def create_clone_spec host
74
+ # Add VM annotation
75
+ configSpec = RbVmomi::VIM.VirtualMachineConfigSpec(
76
+ :annotation =>
77
+ 'Base template: ' + host['template'] + "\n" +
78
+ 'Creation time: ' + Time.now.strftime("%Y-%m-%d %H:%M") + "\n\n" +
79
+ 'CI build link: ' + ( ENV['BUILD_URL'] || 'Deployed independently of CI' ) +
80
+ 'department: ' + @options[:department] +
81
+ 'project: ' + @options[:project],
82
+ :extraConfig => [
83
+ { :key => 'guestinfo.hostname',
84
+ :value => host['vmhostname']
85
+ }
86
+ ]
87
+ )
88
+
89
+ # Are we using a customization spec?
90
+ customizationSpec = @vsphere_helper.find_customization( host['template'] )
91
+
92
+ if customizationSpec
93
+ # Print a logger message if using a customization spec
94
+ @logger.notify "Found customization spec for '#{host['template']}', will apply after boot"
95
+ end
96
+
97
+ # Put the VM in the specified folder and resource pool
98
+ relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec(
99
+ :datastore => @vsphere_helper.find_datastore(@options['datacenter'],@options['datastore']),
100
+ :pool => @options['resourcepool'] ? @vsphere_helper.find_pool(@options['datacenter'],@options['resourcepool']) : nil,
101
+ :diskMoveType => :moveChildMostDiskBacking
102
+ )
103
+
104
+ # Create a clone spec
105
+ spec = RbVmomi::VIM.VirtualMachineCloneSpec(
106
+ :config => configSpec,
107
+ :location => relocateSpec,
108
+ :customization => customizationSpec,
109
+ :powerOn => true,
110
+ :template => false
111
+ )
112
+ spec
113
+ end
114
+
115
+ def provision
116
+ connect_to_vsphere
117
+ begin
118
+
119
+ try = 1
120
+ attempts = @options[:timeout].to_i / 5
121
+
122
+ start = Time.now
123
+ tasks = []
124
+ @hosts.each_with_index do |h, i|
125
+ if h['name']
126
+ h['vmhostname'] = h['name']
127
+ else
128
+ h['vmhostname'] = generate_host_name
129
+ end
130
+
131
+ if h['template'].nil? and defined?(ENV['BEAKER_vcloud_template'])
132
+ h['template'] = ENV['BEAKER_vcloud_template']
133
+ end
134
+
135
+ raise "Missing template configuration for #{h}. Set template in nodeset or set ENV[BEAKER_vcloud_template]" unless h['template']
136
+
137
+ if h['template'] =~ /\//
138
+ templatefolders = h['template'].split('/')
139
+ h['template'] = templatefolders.pop
140
+ end
141
+
142
+ @logger.notify "Deploying #{h['vmhostname']} (#{h.name}) to #{@options['folder']} from template '#{h['template']}'"
143
+
144
+ vm = {}
145
+
146
+ if templatefolders
147
+ vm[h['template']] = @vsphere_helper.find_folder(@options['datacenter'],templatefolders.join('/')).find(h['template'])
148
+ else
149
+ vm = @vsphere_helper.find_vms(h['template'])
150
+ end
151
+
152
+ if vm.length == 0
153
+ raise "Unable to find template '#{h['template']}'!"
154
+ end
155
+
156
+ spec = create_clone_spec(h)
157
+
158
+ # Deploy from specified template
159
+ tasks << vm[h['template']].CloneVM_Task( :folder => @vsphere_helper.find_folder(@options['datacenter'],@options['folder']), :name => h['vmhostname'], :spec => spec )
160
+ end
161
+
162
+ try = (Time.now - start) / 5
163
+ @vsphere_helper.wait_for_tasks(tasks, try, attempts)
164
+ @logger.notify 'Spent %.2f seconds deploying VMs' % (Time.now - start)
165
+
166
+ try = (Time.now - start) / 5
167
+ duration = run_and_report_duration do
168
+ @hosts.each_with_index do |h, i|
169
+ booting_host(h, try, attempts)
170
+ end
171
+ end
172
+ @logger.notify "Spent %.2f seconds booting and waiting for vSphere registration" % duration
173
+
174
+ try = (Time.now - start) / 5
175
+ duration = run_and_report_duration do
176
+ @hosts.each do |host|
177
+ repeat_fibonacci_style_for 8 do
178
+ @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress != nil
179
+ end
180
+ host[:ip] = @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress
181
+ enable_root(host)
182
+ end
183
+ end
184
+
185
+ @logger.notify "Spent %.2f seconds waiting for DNS resolution" % duration
186
+
187
+ rescue => e
188
+ @vsphere_helper.close
189
+ report_and_raise(@logger, e, "Vcloud.provision")
190
+ end
191
+
192
+ end
193
+
194
+ def cleanup
195
+ @logger.notify "Destroying vCloud boxes"
196
+ connect_to_vsphere
197
+
198
+ vm_names = @hosts.map {|h| h['vmhostname'] }.compact
199
+ if @hosts.length != vm_names.length
200
+ @logger.warn "Some hosts did not have vmhostname set correctly! This likely means VM provisioning was not successful"
201
+ end
202
+ vms = @vsphere_helper.find_vms vm_names
203
+ begin
204
+ vm_names.each do |name|
205
+ unless vm = vms[name]
206
+ @logger.warn "Unable to cleanup #{name}, couldn't find VM #{name} in vSphere!"
207
+ next
208
+ end
209
+
210
+ if vm.runtime.powerState == 'poweredOn'
211
+ @logger.notify "Shutting down #{vm.name}"
212
+ duration = run_and_report_duration do
213
+ vm.PowerOffVM_Task.wait_for_completion
214
+ end
215
+ @logger.notify "Spent %.2f seconds halting #{vm.name}" % duration
216
+ end
217
+
218
+ duration = run_and_report_duration do
219
+ vm.Destroy_Task
220
+ end
221
+ @logger.notify "Spent %.2f seconds destroying #{vm.name}" % duration
222
+
223
+ end
224
+ rescue RbVmomi::Fault => ex
225
+ if ex.fault.is_a?(RbVmomi::VIM::ManagedObjectNotFound)
226
+ #it's already gone, don't bother trying to delete it
227
+ name = vms.key(ex.fault.obj)
228
+ vms.delete(name)
229
+ vm_names.delete(name)
230
+ @logger.warn "Unable to destroy #{name}, it was not found in vSphere"
231
+ retry
232
+ end
233
+ end
234
+ @vsphere_helper.close
235
+ end
236
+
237
+ end
238
+ end
@@ -0,0 +1,355 @@
1
+ require 'yaml' unless defined?(YAML)
2
+ require 'json'
3
+ require 'net/http'
4
+
5
+ module Beaker
6
+ class Vmpooler < Beaker::Hypervisor
7
+
8
+ SSH_EXCEPTIONS = [
9
+ SocketError,
10
+ Timeout::Error,
11
+ Errno::ETIMEDOUT,
12
+ Errno::EHOSTDOWN,
13
+ Errno::EHOSTUNREACH,
14
+ Errno::ECONNREFUSED,
15
+ Errno::ECONNRESET,
16
+ Errno::ENETUNREACH,
17
+ ]
18
+
19
+ attr_reader :options, :logger, :hosts, :credentials
20
+
21
+ def initialize(vmpooler_hosts, options)
22
+ @options = options
23
+ @logger = options[:logger]
24
+ @hosts = vmpooler_hosts
25
+ @credentials = load_credentials(@options[:dot_fog])
26
+ end
27
+
28
+ def load_credentials(dot_fog = '.fog')
29
+ creds = {}
30
+
31
+ if fog = read_fog_file(dot_fog)
32
+ if fog[:default] && fog[:default][:vmpooler_token]
33
+ creds[:vmpooler_token] = fog[:default][:vmpooler_token]
34
+ else
35
+ @logger.warn "Credentials file (#{dot_fog}) is missing a :default section with a :vmpooler_token value; proceeding without authentication"
36
+ end
37
+ else
38
+ @logger.warn "Credentials file (#{dot_fog}) is empty; proceeding without authentication"
39
+ end
40
+
41
+ creds
42
+
43
+ rescue TypeError, Psych::SyntaxError => e
44
+ @logger.warn "#{e.class}: Credentials file (#{dot_fog}) has invalid syntax; proceeding without authentication"
45
+ creds
46
+ rescue Errno::ENOENT
47
+ @logger.warn "Credentials file (#{dot_fog}) not found; proceeding without authentication"
48
+ creds
49
+ end
50
+
51
+ def read_fog_file(dot_fog = '.fog')
52
+ YAML.load_file(dot_fog)
53
+ end
54
+
55
+ def check_url url
56
+ begin
57
+ URI.parse(url)
58
+ rescue
59
+ return false
60
+ end
61
+ true
62
+ end
63
+
64
+ def get_template_url pooling_api, template
65
+ if not check_url(pooling_api)
66
+ raise ArgumentError, "Invalid pooling_api URL: #{pooling_api}"
67
+ end
68
+ scheme = ''
69
+ if not URI.parse(pooling_api).scheme
70
+ scheme = 'http://'
71
+ end
72
+ #check that you have a valid uri
73
+ template_url = scheme + pooling_api + '/vm/' + template
74
+ if not check_url(template_url)
75
+ raise ArgumentError, "Invalid full template URL: #{template_url}"
76
+ end
77
+ template_url
78
+ end
79
+
80
+ # Override host tags with presets
81
+ # @param [Beaker::Host] host Beaker host
82
+ # @return [Hash] Tag hash
83
+ def add_tags(host)
84
+ host[:host_tags].merge(
85
+ 'beaker_version' => Beaker::Version::STRING,
86
+ 'jenkins_build_url' => @options[:jenkins_build_url],
87
+ 'department' => @options[:department],
88
+ 'project' => @options[:project],
89
+ 'created_by' => @options[:created_by],
90
+ 'name' => host.name,
91
+ 'roles' => host.host_hash[:roles].join(', ')
92
+ )
93
+ end
94
+
95
+ # Get host info hash from parsed json response
96
+ # @param [Hash] parsed_response hash
97
+ # @param [String] template string
98
+ # @return [Hash] Host info hash
99
+ def get_host_info(parsed_response, template)
100
+ parsed_response[template]
101
+ end
102
+
103
+ def provision
104
+ request_payload = {}
105
+ start = Time.now
106
+
107
+ @hosts.each_with_index do |h, i|
108
+ if not h['template']
109
+ raise ArgumentError, "You must specify a template name for #{h}"
110
+ end
111
+ if h['template'] =~ /\//
112
+ templatefolders = h['template'].split('/')
113
+ h['template'] = templatefolders.pop
114
+ end
115
+
116
+ request_payload[h['template']] = (request_payload[h['template']].to_i + 1).to_s
117
+ end
118
+
119
+ last_wait, wait = 0, 1
120
+ waited = 0 #the amount of time we've spent waiting for this host to provision
121
+ begin
122
+ uri = URI.parse(@options['pooling_api'] + '/vm/')
123
+
124
+ http = Net::HTTP.new(uri.host, uri.port)
125
+ request = Net::HTTP::Post.new(uri.request_uri)
126
+
127
+ if @credentials[:vmpooler_token]
128
+ request['X-AUTH-TOKEN'] = @credentials[:vmpooler_token]
129
+ @logger.notify "Requesting VM set from vmpooler (with authentication token)"
130
+ else
131
+ @logger.notify "Requesting VM set from vmpooler"
132
+ end
133
+
134
+ request_payload_json = request_payload.to_json
135
+ @logger.trace( "Request payload json: #{request_payload_json}" )
136
+ request.body = request_payload_json
137
+
138
+ response = http.request(request)
139
+ parsed_response = JSON.parse(response.body)
140
+ @logger.trace( "Response parsed json: #{parsed_response}" )
141
+
142
+ if parsed_response['ok']
143
+ domain = parsed_response['domain']
144
+ request_payload = {}
145
+
146
+ @hosts.each_with_index do |h, i|
147
+ # If the requested host template is not available on vmpooler
148
+ host_template = h['template']
149
+ if get_host_info(parsed_response, host_template).nil?
150
+ request_payload[host_template] ||= 0
151
+ request_payload[host_template] += 1
152
+ next
153
+ end
154
+ if parsed_response[h['template']]['hostname'].is_a?(Array)
155
+ hostname = parsed_response[host_template]['hostname'].shift
156
+ else
157
+ hostname = parsed_response[host_template]['hostname']
158
+ end
159
+
160
+ h['vmhostname'] = domain ? "#{hostname}.#{domain}" : hostname
161
+
162
+ @logger.notify "Using available host '#{h['vmhostname']}' (#{h.name})"
163
+ end
164
+ unless request_payload.empty?
165
+ raise "Vmpooler.provision - requested VM templates #{request_payload.keys} not available"
166
+ end
167
+ else
168
+ if response.code == '401'
169
+ raise "Vmpooler.provision - response from pooler not ok. Vmpooler token not authorized to make request.\n#{parsed_response}"
170
+ else
171
+ raise "Vmpooler.provision - response from pooler not ok. Requested host set #{request_payload.keys} not available in pooler.\n#{parsed_response}"
172
+ end
173
+ end
174
+ rescue JSON::ParserError, RuntimeError, *SSH_EXCEPTIONS => e
175
+ @logger.debug "Failed vmpooler provision: #{e.class} : #{e.message}"
176
+ if waited <= @options[:timeout].to_i
177
+ @logger.debug("Retrying provision for vmpooler host after waiting #{wait} second(s)")
178
+ sleep wait
179
+ waited += wait
180
+ last_wait, wait = wait, last_wait + wait
181
+ retry
182
+ end
183
+ report_and_raise(@logger, e, 'Vmpooler.provision')
184
+ end
185
+
186
+ @logger.notify 'Spent %.2f seconds grabbing VMs' % (Time.now - start)
187
+
188
+ start = Time.now
189
+ @logger.notify 'Tagging vmpooler VMs'
190
+
191
+ @hosts.each_with_index do |h, i|
192
+ begin
193
+ uri = URI.parse(@options[:pooling_api] + '/vm/' + h['vmhostname'].split('.')[0])
194
+
195
+ http = Net::HTTP.new(uri.host, uri.port)
196
+ request = Net::HTTP::Put.new(uri.request_uri)
197
+
198
+ # merge pre-defined tags with host tags
199
+ request.body = { 'tags' => add_tags(h) }.to_json
200
+
201
+ response = http.request(request)
202
+ rescue RuntimeError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
203
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, *SSH_EXCEPTIONS => e
204
+ @logger.notify "Failed to connect to vmpooler for tagging!"
205
+ end
206
+
207
+ begin
208
+ parsed_response = JSON.parse(response.body)
209
+
210
+ unless parsed_response['ok']
211
+ @logger.notify "Failed to tag host '#{h['vmhostname']}'!"
212
+ end
213
+ rescue JSON::ParserError => e
214
+ @logger.notify "Failed to tag host '#{h['vmhostname']}'! (failed with #{e.class})"
215
+ end
216
+ end
217
+
218
+ @logger.notify 'Spent %.2f seconds tagging VMs' % (Time.now - start)
219
+
220
+ # add additional disks to vm
221
+ @logger.debug 'Looking for disks to add...'
222
+
223
+ @hosts.each do |h|
224
+ hostname = h['vmhostname'].split(".")[0]
225
+
226
+ if h['disks']
227
+ @logger.debug "Found disks for #{hostname}!"
228
+ disks = h['disks']
229
+
230
+ disks.each_with_index do |disk_size, index|
231
+ start = Time.now
232
+
233
+ add_disk(hostname, disk_size)
234
+
235
+ done = wait_for_disk(hostname, disk_size, index)
236
+ if done
237
+ @logger.notify "Spent %.2f seconds adding disk #{index}. " % (Time.now - start)
238
+ else
239
+ raise "Could not verify disk was added after %.2f seconds" % (Time.now - start)
240
+ end
241
+ end
242
+ else
243
+ @logger.debug "No disks to add for #{hostname}"
244
+ end
245
+ end
246
+ end
247
+
248
+ def cleanup
249
+ vm_names = @hosts.map {|h| h['vmhostname'] }.compact
250
+ if @hosts.length != vm_names.length
251
+ @logger.warn "Some hosts did not have vmhostname set correctly! This likely means VM provisioning was not successful"
252
+ end
253
+
254
+ start = Time.now
255
+ vm_names.each do |name|
256
+ @logger.notify "Handing '#{name}' back to vmpooler for VM destruction"
257
+
258
+ uri = URI.parse(get_template_url(@options['pooling_api'], name))
259
+
260
+ http = Net::HTTP.new( uri.host, uri.port )
261
+ request = Net::HTTP::Delete.new(uri.request_uri)
262
+
263
+ if @credentials[:vmpooler_token]
264
+ request['X-AUTH-TOKEN'] = @credentials[:vmpooler_token]
265
+ end
266
+
267
+ begin
268
+ response = http.request(request)
269
+ rescue *SSH_EXCEPTIONS => e
270
+ report_and_raise(@logger, e, 'Vmpooler.cleanup (http.request)')
271
+ end
272
+ end
273
+
274
+ @logger.notify "Spent %.2f seconds cleaning up" % (Time.now - start)
275
+ end
276
+
277
+ def add_disk(hostname, disk_size)
278
+ @logger.notify "Requesting an additional disk of size #{disk_size}GB for #{hostname}"
279
+
280
+ if !disk_size.to_s.match /[0123456789]/ || size <= '0'
281
+ raise NameError.new "Disk size must be an integer greater than zero!"
282
+ end
283
+
284
+ begin
285
+ uri = URI.parse(@options[:pooling_api] + '/api/v1/vm/' + hostname + '/disk/' + disk_size.to_s)
286
+
287
+ http = Net::HTTP.new(uri.host, uri.port)
288
+ request = Net::HTTP::Post.new(uri.request_uri)
289
+ request['X-AUTH-TOKEN'] = @credentials[:vmpooler_token]
290
+
291
+ response = http.request(request)
292
+
293
+ parsed = parse_response(response)
294
+
295
+ raise "Response from #{hostname} indicates disk was not added" if !parsed['ok']
296
+
297
+ rescue NameError, RuntimeError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
298
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, *SSH_EXCEPTIONS => e
299
+ report_and_raise(@logger, e, 'Vmpooler.add_disk')
300
+ end
301
+ end
302
+
303
+ def parse_response(response)
304
+ parsed_response = JSON.parse(response.body)
305
+ end
306
+
307
+ def disk_added?(host, disk_size, index)
308
+ if host['disk'].nil?
309
+ false
310
+ else
311
+ host['disk'][index] == "+#{disk_size}gb"
312
+ end
313
+ end
314
+
315
+ def get_vm(hostname)
316
+ begin
317
+ uri = URI.parse(@options[:pooling_api] + '/vm/' + hostname)
318
+
319
+ http = Net::HTTP.new(uri.host, uri.port)
320
+ request = Net::HTTP::Get.new(uri.request_uri)
321
+
322
+ response = http.request(request)
323
+ rescue RuntimeError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
324
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, *SSH_EXCEPTIONS => e
325
+ @logger.notify "Failed to connect to vmpooler while getting VM information!"
326
+ end
327
+ end
328
+
329
+ def wait_for_disk(hostname, disk_size, index)
330
+ response = get_vm(hostname)
331
+ parsed = parse_response(response)
332
+
333
+ @logger.notify "Waiting for disk"
334
+
335
+ attempts = 0
336
+
337
+ while (!disk_added?(parsed[hostname], disk_size, index) && attempts < 20)
338
+ sleep 10
339
+ begin
340
+ response = get_vm(hostname)
341
+ parsed = parse_response(response)
342
+ rescue RuntimeError, Errno::EINVAL, Errno::ECONNRESET, EOFError,
343
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, *SSH_EXCEPTIONS => e
344
+ report_and_raise(@logger, e, "Vmpooler.wait_for_disk")
345
+ end
346
+ print "."
347
+ attempts += 1
348
+ end
349
+
350
+ puts " "
351
+
352
+ disk_added?(parsed[hostname], disk_size, index)
353
+ end
354
+ end
355
+ end
@@ -0,0 +1,3 @@
1
+ module BeakerVmpooler
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ module Beaker
4
+ describe Vcloud do
5
+
6
+ before :each do
7
+ MockVsphereHelper.set_config( fog_file_contents )
8
+ MockVsphereHelper.set_vms( make_hosts() )
9
+ stub_const( "VsphereHelper", MockVsphereHelper )
10
+ stub_const( "Net", MockNet )
11
+ json = double( 'json' )
12
+ allow( json ).to receive( :parse ) do |arg|
13
+ arg
14
+ end
15
+ stub_const( "JSON", json )
16
+ allow( Socket ).to receive( :getaddrinfo ).and_return( true )
17
+ end
18
+
19
+ describe "#provision" do
20
+
21
+ it 'instantiates vmpooler if pooling api is provided' do
22
+ opts = make_opts
23
+ opts[:pooling_api] = 'testpool'
24
+ hypervisor = Beaker::Vcloud.new( make_hosts, opts)
25
+ expect( hypervisor.class ).to be Beaker::Vmpooler
26
+ end
27
+
28
+ it 'provisions hosts and add them to the pool' do
29
+ MockVsphereHelper.powerOff
30
+
31
+ opts = make_opts
32
+ opts[:pooling_api] = nil
33
+ opts[:datacenter] = 'testdc'
34
+
35
+ vcloud = Beaker::Vcloud.new( make_hosts, opts )
36
+ allow( vcloud ).to receive( :require ).and_return( true )
37
+ allow( vcloud ).to receive( :sleep ).and_return( true )
38
+ vcloud.provision
39
+
40
+ hosts = vcloud.instance_variable_get( :@hosts )
41
+ hosts.each do | host |
42
+ name = host['vmhostname']
43
+ vm = MockVsphereHelper.find_vm( name )
44
+ expect( vm.toolsRunningStatus ).to be === "guestToolsRunning"
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+
51
+ describe "#cleanup" do
52
+
53
+ it "cleans up hosts not in the pool" do
54
+ MockVsphereHelper.powerOn
55
+
56
+ opts = make_opts
57
+ opts[:pooling_api] = nil
58
+ opts[:datacenter] = 'testdc'
59
+
60
+ vcloud = Beaker::Vcloud.new( make_hosts, opts )
61
+ allow( vcloud ).to receive( :require ).and_return( true )
62
+ allow( vcloud ).to receive( :sleep ).and_return( true )
63
+ vcloud.provision
64
+ vcloud.cleanup
65
+
66
+ hosts = vcloud.instance_variable_get( :@hosts )
67
+ vm_names = hosts.map {|h| h['vmhostname'] }.compact
68
+ vm_names.each do | name |
69
+ vm = MockVsphereHelper.find_vm( name )
70
+ expect( vm.runtime.powerState ).to be === "poweredOff"
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
79
+ end