beaker-vcloud 1.1.0 → 2.0.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +9 -0
- data/.github/workflows/release.yml +2 -2
- data/.github/workflows/test.yml +30 -7
- data/.rubocop.yml +5 -0
- data/.rubocop_todo.yml +292 -0
- data/CHANGELOG.md +25 -0
- data/Gemfile +6 -22
- data/Rakefile +12 -111
- data/beaker-vcloud.gemspec +19 -17
- data/bin/beaker-vcloud +1 -3
- data/lib/beaker/hypervisor/vcloud.rb +79 -88
- data/lib/beaker-vcloud/version.rb +1 -1
- data/spec/beaker/hypervisor/vcloud_spec.rb +39 -49
- data/spec/mock_vsphere.rb +284 -0
- data/spec/mock_vsphere_helper.rb +167 -0
- data/spec/spec_helper.rb +8 -6
- metadata +72 -28
data/bin/beaker-vcloud
CHANGED
@@ -4,13 +4,12 @@ require 'rbvmomi'
|
|
4
4
|
|
5
5
|
module Beaker
|
6
6
|
class Vcloud < Beaker::Hypervisor
|
7
|
-
|
8
7
|
def self.new(vcloud_hosts, options)
|
9
8
|
# Warning for pre-vmpooler style hosts configuration. TODO: remove this eventually.
|
10
9
|
if options['pooling_api'] && !options['datacenter']
|
11
|
-
options[:logger].warn
|
12
|
-
|
13
|
-
|
10
|
+
options[:logger].warn 'It looks like you may be trying to access vmpooler with `hypervisor: vcloud`. ' \
|
11
|
+
'This functionality has been removed. Change your hosts to `hypervisor: vmpooler` ' \
|
12
|
+
'and remove unused :datacenter, :folder, and :datastore from CONFIG.'
|
14
13
|
end
|
15
14
|
super
|
16
15
|
end
|
@@ -23,76 +22,71 @@ module Beaker
|
|
23
22
|
raise 'You must specify a datastore for vCloud instances!' unless @options['datastore']
|
24
23
|
raise 'You must specify a folder for vCloud instances!' unless @options['folder']
|
25
24
|
raise 'You must specify a datacenter for vCloud instances!' unless @options['datacenter']
|
25
|
+
|
26
26
|
@vcenter_credentials = get_fog_credentials(@options[:dot_fog], @options[:vcenter_instance] || :default)
|
27
27
|
end
|
28
28
|
|
29
29
|
def connect_to_vsphere
|
30
30
|
@logger.notify "Connecting to vSphere at #{@vcenter_credentials[:vsphere_server]}" +
|
31
|
-
|
31
|
+
" with credentials for #{@vcenter_credentials[:vsphere_username]}"
|
32
32
|
|
33
|
-
@vsphere_helper = VsphereHelper.new :
|
34
|
-
:
|
35
|
-
:
|
33
|
+
@vsphere_helper = VsphereHelper.new server: @vcenter_credentials[:vsphere_server],
|
34
|
+
user: @vcenter_credentials[:vsphere_username],
|
35
|
+
pass: @vcenter_credentials[:vsphere_password]
|
36
36
|
end
|
37
37
|
|
38
|
-
def wait_for_dns_resolution
|
38
|
+
def wait_for_dns_resolution(host, try, attempts)
|
39
39
|
@logger.notify "Waiting for #{host['vmhostname']} DNS resolution"
|
40
40
|
begin
|
41
41
|
Socket.getaddrinfo(host['vmhostname'], nil)
|
42
|
-
rescue
|
43
|
-
|
44
|
-
sleep 5
|
45
|
-
try += 1
|
42
|
+
rescue StandardError
|
43
|
+
raise "DNS resolution failed after #{@options[:timeout].to_i} seconds" unless try <= attempts
|
46
44
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
sleep 5
|
46
|
+
try += 1
|
47
|
+
|
48
|
+
retry
|
51
49
|
end
|
52
50
|
end
|
53
51
|
|
54
|
-
def booting_host
|
52
|
+
def booting_host(host, try, attempts)
|
55
53
|
@logger.notify "Booting #{host['vmhostname']} (#{host.name}) and waiting for it to register with vSphere"
|
56
|
-
until
|
57
|
-
|
58
|
-
@
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
raise "vSphere registration failed after #{@options[:timeout].to_i} seconds"
|
64
|
-
end
|
54
|
+
until @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.toolsRunningStatus == 'guestToolsRunning' and
|
55
|
+
!@vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress.nil?
|
56
|
+
raise "vSphere registration failed after #{@options[:timeout].to_i} seconds" unless try <= attempts
|
57
|
+
|
58
|
+
sleep 5
|
59
|
+
try += 1
|
60
|
+
|
65
61
|
end
|
66
62
|
end
|
67
63
|
|
68
64
|
# Directly borrowed from openstack hypervisor
|
69
65
|
def enable_root(host)
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
66
|
+
return unless host['user'] != 'root'
|
67
|
+
|
68
|
+
copy_ssh_to_root(host, @options)
|
69
|
+
enable_root_login(host, @options)
|
70
|
+
host['user'] = 'root'
|
71
|
+
host.close
|
76
72
|
end
|
77
73
|
|
78
|
-
def create_clone_spec
|
74
|
+
def create_clone_spec(host)
|
79
75
|
# Add VM annotation
|
80
76
|
configSpec = RbVmomi::VIM.VirtualMachineConfigSpec(
|
81
|
-
:
|
82
|
-
'
|
83
|
-
'
|
84
|
-
'CI build link: ' + ( ENV['BUILD_URL'] || 'Deployed independently of CI' ) +
|
77
|
+
annotation: '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') +
|
85
80
|
'department: ' + @options[:department] +
|
86
81
|
'project: ' + @options[:project],
|
87
|
-
:
|
88
|
-
{ :
|
89
|
-
:
|
90
|
-
|
91
|
-
]
|
82
|
+
extraConfig: [
|
83
|
+
{ key: 'guestinfo.hostname',
|
84
|
+
value: host['vmhostname'], },
|
85
|
+
],
|
92
86
|
)
|
93
87
|
|
94
88
|
# Are we using a customization spec?
|
95
|
-
customizationSpec = @vsphere_helper.find_customization(
|
89
|
+
customizationSpec = @vsphere_helper.find_customization(host['template'])
|
96
90
|
|
97
91
|
if customizationSpec
|
98
92
|
# Print a logger message if using a customization spec
|
@@ -101,45 +95,44 @@ module Beaker
|
|
101
95
|
|
102
96
|
# Put the VM in the specified folder and resource pool
|
103
97
|
relocateSpec = RbVmomi::VIM.VirtualMachineRelocateSpec(
|
104
|
-
:
|
105
|
-
:
|
106
|
-
|
98
|
+
datastore: @vsphere_helper.find_datastore(@options['datacenter'], @options['datastore']),
|
99
|
+
pool: if @options['resourcepool']
|
100
|
+
@vsphere_helper.find_pool(@options['datacenter'],
|
101
|
+
@options['resourcepool'])
|
102
|
+
end,
|
103
|
+
diskMoveType: :moveChildMostDiskBacking,
|
107
104
|
)
|
108
105
|
|
109
106
|
# Create a clone spec
|
110
|
-
|
111
|
-
:
|
112
|
-
:
|
113
|
-
:
|
114
|
-
:
|
115
|
-
:
|
107
|
+
RbVmomi::VIM.VirtualMachineCloneSpec(
|
108
|
+
config: configSpec,
|
109
|
+
location: relocateSpec,
|
110
|
+
customization: customizationSpec,
|
111
|
+
powerOn: true,
|
112
|
+
template: false,
|
116
113
|
)
|
117
|
-
spec
|
118
114
|
end
|
119
115
|
|
120
116
|
def provision
|
121
117
|
connect_to_vsphere
|
122
118
|
begin
|
123
|
-
|
124
119
|
try = 1
|
125
120
|
attempts = @options[:timeout].to_i / 5
|
126
121
|
|
127
122
|
start = Time.now
|
128
123
|
tasks = []
|
129
|
-
@hosts.each_with_index do |h,
|
130
|
-
|
131
|
-
h['vmhostname'] = h['name']
|
132
|
-
else
|
133
|
-
h['vmhostname'] = generate_host_name
|
134
|
-
end
|
124
|
+
@hosts.each_with_index do |h, _i|
|
125
|
+
h['vmhostname'] = (h['name'] || generate_host_name)
|
135
126
|
|
136
|
-
if h['template'].nil? and defined?(ENV
|
137
|
-
h['template'] = ENV
|
127
|
+
if h['template'].nil? and defined?(ENV.fetch('BEAKER_vcloud_template', nil))
|
128
|
+
h['template'] = ENV.fetch('BEAKER_vcloud_template', nil)
|
138
129
|
end
|
139
130
|
|
140
|
-
|
131
|
+
unless h['template']
|
132
|
+
raise "Missing template configuration for #{h}. Set template in nodeset or set ENV[BEAKER_vcloud_template]"
|
133
|
+
end
|
141
134
|
|
142
|
-
if h['template']
|
135
|
+
if %r{/}.match?(h['template'])
|
143
136
|
templatefolders = h['template'].split('/')
|
144
137
|
h['template'] = templatefolders.pop
|
145
138
|
end
|
@@ -149,60 +142,60 @@ module Beaker
|
|
149
142
|
vm = {}
|
150
143
|
|
151
144
|
if templatefolders
|
152
|
-
vm[h['template']] =
|
145
|
+
vm[h['template']] =
|
146
|
+
@vsphere_helper.find_folder(@options['datacenter'], templatefolders.join('/')).find(h['template'])
|
153
147
|
else
|
154
148
|
vm = @vsphere_helper.find_vms(h['template'])
|
155
149
|
end
|
156
150
|
|
157
|
-
if vm.length == 0
|
158
|
-
raise "Unable to find template '#{h['template']}'!"
|
159
|
-
end
|
151
|
+
raise "Unable to find template '#{h['template']}'!" if vm.length == 0
|
160
152
|
|
161
153
|
spec = create_clone_spec(h)
|
162
154
|
|
163
155
|
# Deploy from specified template
|
164
|
-
tasks << vm[h['template']].CloneVM_Task(
|
156
|
+
tasks << vm[h['template']].CloneVM_Task(
|
157
|
+
folder: @vsphere_helper.find_folder(@options['datacenter'],
|
158
|
+
@options['folder']), name: h['vmhostname'], spec: spec
|
159
|
+
)
|
165
160
|
end
|
166
161
|
|
167
162
|
try = (Time.now - start) / 5
|
168
163
|
@vsphere_helper.wait_for_tasks(tasks, try, attempts)
|
169
|
-
@logger.notify 'Spent %.2f seconds deploying VMs'
|
164
|
+
@logger.notify format('Spent %.2f seconds deploying VMs', (Time.now - start))
|
170
165
|
|
171
166
|
try = (Time.now - start) / 5
|
172
167
|
duration = run_and_report_duration do
|
173
|
-
@hosts.each_with_index do |h,
|
168
|
+
@hosts.each_with_index do |h, _i|
|
174
169
|
booting_host(h, try, attempts)
|
175
170
|
end
|
176
171
|
end
|
177
|
-
@logger.notify
|
172
|
+
@logger.notify 'Spent %.2f seconds booting and waiting for vSphere registration' % duration
|
178
173
|
|
179
174
|
try = (Time.now - start) / 5
|
180
175
|
duration = run_and_report_duration do
|
181
176
|
@hosts.each do |host|
|
182
177
|
repeat_fibonacci_style_for 8 do
|
183
|
-
|
178
|
+
!@vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress.nil?
|
184
179
|
end
|
185
180
|
host[:ip] = @vsphere_helper.find_vms(host['vmhostname'])[host['vmhostname']].summary.guest.ipAddress
|
186
181
|
enable_root(host) unless host.is_cygwin?
|
187
182
|
end
|
188
183
|
end
|
189
184
|
|
190
|
-
@logger.notify
|
191
|
-
|
192
|
-
rescue => e
|
185
|
+
@logger.notify 'Spent %.2f seconds waiting for DNS resolution' % duration
|
186
|
+
rescue StandardError => e
|
193
187
|
@vsphere_helper.close
|
194
|
-
report_and_raise(@logger, e,
|
188
|
+
report_and_raise(@logger, e, 'Vcloud.provision')
|
195
189
|
end
|
196
|
-
|
197
190
|
end
|
198
191
|
|
199
192
|
def cleanup
|
200
|
-
@logger.notify
|
193
|
+
@logger.notify 'Destroying vCloud boxes'
|
201
194
|
connect_to_vsphere
|
202
195
|
|
203
|
-
vm_names = @hosts.map {|h| h['vmhostname'] }.compact
|
196
|
+
vm_names = @hosts.map { |h| h['vmhostname'] }.compact
|
204
197
|
if @hosts.length != vm_names.length
|
205
|
-
@logger.warn
|
198
|
+
@logger.warn 'Some hosts did not have vmhostname set correctly! This likely means VM provisioning was not successful'
|
206
199
|
end
|
207
200
|
vms = @vsphere_helper.find_vms vm_names
|
208
201
|
begin
|
@@ -224,12 +217,11 @@ module Beaker
|
|
224
217
|
vm.Destroy_Task
|
225
218
|
end
|
226
219
|
@logger.notify "Spent %.2f seconds destroying #{vm.name}" % duration
|
227
|
-
|
228
220
|
end
|
229
|
-
rescue RbVmomi::Fault =>
|
230
|
-
if
|
231
|
-
#it's already gone, don't bother trying to delete it
|
232
|
-
name = vms.key(
|
221
|
+
rescue RbVmomi::Fault => e
|
222
|
+
if e.fault.is_a?(RbVmomi::VIM::ManagedObjectNotFound)
|
223
|
+
# it's already gone, don't bother trying to delete it
|
224
|
+
name = vms.key(e.fault.obj)
|
233
225
|
vms.delete(name)
|
234
226
|
vm_names.delete(name)
|
235
227
|
@logger.warn "Unable to destroy #{name}, it was not found in vSphere"
|
@@ -238,6 +230,5 @@ module Beaker
|
|
238
230
|
end
|
239
231
|
@vsphere_helper.close
|
240
232
|
end
|
241
|
-
|
242
233
|
end
|
243
234
|
end
|
@@ -2,43 +2,41 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Beaker
|
4
4
|
describe Vcloud do
|
5
|
-
|
6
|
-
|
7
|
-
MockVsphereHelper.
|
8
|
-
|
9
|
-
stub_const(
|
10
|
-
|
11
|
-
json
|
12
|
-
allow( json ).to receive( :parse ) do |arg|
|
5
|
+
before do
|
6
|
+
MockVsphereHelper.set_config(fog_file_contents)
|
7
|
+
MockVsphereHelper.set_vms(make_hosts)
|
8
|
+
stub_const('VsphereHelper', MockVsphereHelper)
|
9
|
+
stub_const('Net', MockNet)
|
10
|
+
json = double('json')
|
11
|
+
allow(json).to receive(:parse) do |arg|
|
13
12
|
arg
|
14
13
|
end
|
15
|
-
stub_const(
|
16
|
-
allow(
|
17
|
-
allow_any_instance_of(
|
18
|
-
allow_any_instance_of(
|
14
|
+
stub_const('JSON', json)
|
15
|
+
allow(Socket).to receive(:getaddrinfo).and_return(true)
|
16
|
+
allow_any_instance_of(Beaker::Shared).to receive(:get_fog_credentials).and_return(fog_file_contents)
|
17
|
+
allow_any_instance_of(VsphereHelper).to receive(:new).and_return(MockVsphereHelper)
|
19
18
|
end
|
20
19
|
|
21
|
-
describe
|
22
|
-
|
20
|
+
describe '#provision' do
|
23
21
|
it 'warns about deprecated behavior if pooling_api is provided' do
|
24
22
|
opts = make_opts
|
25
23
|
opts[:pooling_api] = 'testpool'
|
26
|
-
expect(
|
27
|
-
expect{ Beaker::Vcloud.new(
|
24
|
+
expect(opts[:logger]).to receive(:warn).once
|
25
|
+
expect { Beaker::Vcloud.new(make_hosts, opts) }.to raise_error(/datacenter/)
|
28
26
|
end
|
29
27
|
|
30
28
|
it 'does not instantiate vmpooler if pooling_api is provided' do
|
31
29
|
opts = make_opts
|
32
30
|
opts[:pooling_api] = 'testpool'
|
33
|
-
expect{ Beaker::Vcloud.new(
|
31
|
+
expect { Beaker::Vcloud.new(make_hosts, opts) }.to raise_error(/datacenter/)
|
34
32
|
end
|
35
33
|
|
36
34
|
it 'ignores pooling_api and instantiates self' do
|
37
35
|
opts = make_opts
|
38
36
|
opts[:pooling_api] = 'testpool'
|
39
37
|
opts[:datacenter] = 'testdatacenter'
|
40
|
-
hypervisor = Beaker::Vcloud.new(
|
41
|
-
expect(
|
38
|
+
hypervisor = Beaker::Vcloud.new(make_hosts, opts)
|
39
|
+
expect(hypervisor.class).to be Beaker::Vcloud
|
42
40
|
end
|
43
41
|
|
44
42
|
it 'provisions hosts and add them to the pool' do
|
@@ -48,18 +46,17 @@ module Beaker
|
|
48
46
|
opts[:pooling_api] = nil
|
49
47
|
opts[:datacenter] = 'testdc'
|
50
48
|
|
51
|
-
vcloud = Beaker::Vcloud.new(
|
52
|
-
allow(
|
53
|
-
allow(
|
49
|
+
vcloud = Beaker::Vcloud.new(make_hosts, opts)
|
50
|
+
allow(vcloud).to receive(:require).and_return(true)
|
51
|
+
allow(vcloud).to receive(:sleep).and_return(true)
|
54
52
|
vcloud.provision
|
55
53
|
|
56
|
-
hosts = vcloud.instance_variable_get(
|
57
|
-
hosts.each do |
|
54
|
+
hosts = vcloud.instance_variable_get(:@hosts)
|
55
|
+
hosts.each do |host|
|
58
56
|
name = host['vmhostname']
|
59
|
-
vm = MockVsphereHelper.find_vm(
|
60
|
-
expect(
|
57
|
+
vm = MockVsphereHelper.find_vm(name)
|
58
|
+
expect(vm.toolsRunningStatus).to be === 'guestToolsRunning'
|
61
59
|
end
|
62
|
-
|
63
60
|
end
|
64
61
|
|
65
62
|
it 'does not run enable_root on cygwin hosts' do
|
@@ -71,44 +68,37 @@ module Beaker
|
|
71
68
|
|
72
69
|
hosts = make_hosts
|
73
70
|
hosts.each do |host|
|
74
|
-
allow(
|
71
|
+
allow(host).to receive(:is_cygwin?).and_return(true)
|
75
72
|
end
|
76
|
-
vcloud = Beaker::Vcloud.new(
|
77
|
-
allow(
|
78
|
-
allow(
|
79
|
-
expect(
|
73
|
+
vcloud = Beaker::Vcloud.new(hosts, opts)
|
74
|
+
allow(vcloud).to receive(:require).and_return(true)
|
75
|
+
allow(vcloud).to receive(:sleep).and_return(true)
|
76
|
+
expect(vcloud).not_to receive(:enable_root)
|
80
77
|
vcloud.provision
|
81
|
-
|
82
78
|
end
|
83
|
-
|
84
79
|
end
|
85
80
|
|
86
|
-
describe
|
87
|
-
|
88
|
-
it "cleans up hosts not in the pool" do
|
81
|
+
describe '#cleanup' do
|
82
|
+
it 'cleans up hosts not in the pool' do
|
89
83
|
MockVsphereHelper.powerOn
|
90
84
|
|
91
85
|
opts = make_opts
|
92
86
|
opts[:pooling_api] = nil
|
93
87
|
opts[:datacenter] = 'testdc'
|
94
88
|
|
95
|
-
vcloud = Beaker::Vcloud.new(
|
96
|
-
allow(
|
97
|
-
allow(
|
89
|
+
vcloud = Beaker::Vcloud.new(make_hosts, opts)
|
90
|
+
allow(vcloud).to receive(:require).and_return(true)
|
91
|
+
allow(vcloud).to receive(:sleep).and_return(true)
|
98
92
|
vcloud.provision
|
99
93
|
vcloud.cleanup
|
100
94
|
|
101
|
-
hosts = vcloud.instance_variable_get(
|
102
|
-
vm_names = hosts.map {|h| h['vmhostname'] }.compact
|
103
|
-
vm_names.each do |
|
104
|
-
vm = MockVsphereHelper.find_vm(
|
105
|
-
expect(
|
95
|
+
hosts = vcloud.instance_variable_get(:@hosts)
|
96
|
+
vm_names = hosts.map { |h| h['vmhostname'] }.compact
|
97
|
+
vm_names.each do |name|
|
98
|
+
vm = MockVsphereHelper.find_vm(name)
|
99
|
+
expect(vm.runtime.powerState).to be === 'poweredOff'
|
106
100
|
end
|
107
|
-
|
108
101
|
end
|
109
|
-
|
110
102
|
end
|
111
|
-
|
112
103
|
end
|
113
|
-
|
114
104
|
end
|