beaker 3.19.0 → 3.20.0

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