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