beaker-vmpooler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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