chef-provisioning-vsphere 0.8.3.dev.2 → 0.8.3
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/.gitignore +2 -2
- data/Gemfile +5 -5
- data/LICENSE +19 -19
- data/LICENSE-Rally +20 -20
- data/README.md +269 -269
- data/Rakefile +22 -22
- data/chef-provisioning-vsphere.gemspec +28 -28
- data/contribution-notice +7 -7
- data/lib/chef/provisioning/driver_init/vsphere.rb +2 -2
- data/lib/chef/provisioning/vsphere_driver.rb +14 -14
- data/lib/chef/provisioning/vsphere_driver/clone_spec_builder.rb +205 -205
- data/lib/chef/provisioning/vsphere_driver/driver.rb +679 -679
- data/lib/chef/provisioning/vsphere_driver/version.rb +3 -3
- data/lib/chef/provisioning/vsphere_driver/vsphere_helpers.rb +341 -341
- data/lib/chef/provisioning/vsphere_driver/vsphere_url.rb +45 -45
- data/lib/kitchen/driver/vsphere.rb +104 -104
- data/spec/integration_tests/.gitignore +1 -1
- data/spec/integration_tests/vsphere_driver_spec.rb +158 -158
- data/spec/unit_tests/VsphereDriver_spec.rb +132 -132
- data/spec/unit_tests/VsphereUrl_spec.rb +65 -65
- data/spec/unit_tests/clone_spec_builder_spec.rb +161 -161
- data/spec/unit_tests/support/fake_action_handler.rb +7 -7
- data/spec/unit_tests/support/vsphere_helper_stub.rb +52 -52
- metadata +4 -4
@@ -1,679 +1,679 @@
|
|
1
|
-
require 'chef'
|
2
|
-
require 'cheffish/merged_config'
|
3
|
-
require 'chef/provisioning/driver'
|
4
|
-
require 'chef/provisioning/machine/windows_machine'
|
5
|
-
require 'chef/provisioning/machine/unix_machine'
|
6
|
-
require 'chef/provisioning/vsphere_driver/clone_spec_builder'
|
7
|
-
require 'chef/provisioning/vsphere_driver/version'
|
8
|
-
require 'chef/provisioning/vsphere_driver/vsphere_helpers'
|
9
|
-
require 'chef/provisioning/vsphere_driver/vsphere_url'
|
10
|
-
|
11
|
-
module ChefProvisioningVsphere
|
12
|
-
# Provisions machines in vSphere.
|
13
|
-
class VsphereDriver < Chef::Provisioning::Driver
|
14
|
-
include Chef::Mixin::ShellOut
|
15
|
-
|
16
|
-
def self.from_url(driver_url, config)
|
17
|
-
VsphereDriver.new(driver_url, config)
|
18
|
-
end
|
19
|
-
|
20
|
-
# Create a new Vsphere provisioner.
|
21
|
-
#
|
22
|
-
# ## Parameters
|
23
|
-
# connect_options - hash of options to be passed to RbVmomi::VIM.connect
|
24
|
-
# :host - required - hostname of the vSphere API server
|
25
|
-
# :port - optional - port on the vSphere API server (default: 443)
|
26
|
-
# :path - optional - path on the vSphere API server (default: /sdk)
|
27
|
-
# :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
|
28
|
-
# :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
|
29
|
-
# :user - required - user name to use in connection to vSphere API server
|
30
|
-
# :password - required - password to use in connection to vSphere API server
|
31
|
-
def self.canonicalize_url(driver_url, config)
|
32
|
-
config = symbolize_keys(config)
|
33
|
-
[ driver_url || URI::VsphereUrl.from_config(config).to_s, config ]
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.symbolize_keys(h)
|
37
|
-
Hash === h ?
|
38
|
-
Hash[
|
39
|
-
h.map do |k, v|
|
40
|
-
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
|
41
|
-
end
|
42
|
-
] : h
|
43
|
-
end
|
44
|
-
|
45
|
-
def initialize(driver_url, config)
|
46
|
-
super(driver_url, config)
|
47
|
-
|
48
|
-
uri = URI(driver_url)
|
49
|
-
@connect_options = {
|
50
|
-
provider: 'vsphere',
|
51
|
-
host: uri.host,
|
52
|
-
port: uri.port,
|
53
|
-
use_ssl: uri.use_ssl,
|
54
|
-
insecure: uri.insecure,
|
55
|
-
path: uri.path
|
56
|
-
}
|
57
|
-
|
58
|
-
if driver_options
|
59
|
-
@connect_options[:user] = driver_options[:user]
|
60
|
-
@connect_options[:password] = driver_options[:password]
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
attr_reader :connect_options
|
65
|
-
|
66
|
-
# Acquire a machine, generally by provisioning it. Returns a Machine
|
67
|
-
# object pointing at the machine, allowing useful actions like setup,
|
68
|
-
# converge, execute, file and directory. The Machine object will have a
|
69
|
-
# "node" property which must be saved to the server (if it is any
|
70
|
-
# different from the original node object).
|
71
|
-
#
|
72
|
-
# ## Parameters
|
73
|
-
# action_handler - the action_handler object that is calling this method; this
|
74
|
-
# is generally a action_handler, but could be anything that can support the
|
75
|
-
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
76
|
-
# kitchen metal driver for acquiring and destroying VMs; see the base
|
77
|
-
# class for what needs providing).
|
78
|
-
# node - node object (deserialized json) representing this machine. If
|
79
|
-
# the node has a provisioner_options hash in it, these will be used
|
80
|
-
# instead of options provided by the provisioner. TODO compare and
|
81
|
-
# fail if different?
|
82
|
-
# node will have node['normal']['provisioner_options'] in it with any options.
|
83
|
-
# It is a hash with this format:
|
84
|
-
#
|
85
|
-
# -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
|
86
|
-
# -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
|
87
|
-
# :datacenter
|
88
|
-
# :resource_pool
|
89
|
-
# :cluster
|
90
|
-
# :datastore
|
91
|
-
# :template_name
|
92
|
-
# :template_folder
|
93
|
-
# :vm_folder
|
94
|
-
# :winrm {...} (not yet implemented)
|
95
|
-
# :ssh {...}
|
96
|
-
#
|
97
|
-
# Example bootstrap_options for vSphere:
|
98
|
-
# TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
|
99
|
-
# 'bootstrap_options' => {
|
100
|
-
# 'template_name' =>'centos6.small',
|
101
|
-
# 'template_folder' =>'Templates',
|
102
|
-
# 'vm_folder' => 'MyApp'
|
103
|
-
# }
|
104
|
-
#
|
105
|
-
# node['normal']['provisioner_output'] will be populated with information
|
106
|
-
# about the created machine. For vSphere, it is a hash with this
|
107
|
-
# format:
|
108
|
-
#
|
109
|
-
# -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
|
110
|
-
# -- vm_folder: name of the vSphere folder containing the VM
|
111
|
-
#
|
112
|
-
def allocate_machine(action_handler, machine_spec, machine_options)
|
113
|
-
merge_options! machine_options
|
114
|
-
|
115
|
-
if machine_spec.location
|
116
|
-
Chef::Log.warn(
|
117
|
-
"Checking to see if #{machine_spec.location} has been created...")
|
118
|
-
vm = vm_for(machine_spec)
|
119
|
-
if vm
|
120
|
-
Chef::Log.warn 'returning existing machine'
|
121
|
-
return vm
|
122
|
-
else
|
123
|
-
Chef::Log.warn machine_msg(
|
124
|
-
machine_spec.name,
|
125
|
-
machine_spec.location['server_id'],
|
126
|
-
'no longer exists. Recreating ...'
|
127
|
-
)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
bootstrap_options = machine_options[:bootstrap_options]
|
131
|
-
|
132
|
-
action_handler.report_progress full_description(
|
133
|
-
machine_spec, bootstrap_options)
|
134
|
-
|
135
|
-
vm = find_or_create_vm(bootstrap_options, machine_spec, action_handler)
|
136
|
-
|
137
|
-
add_machine_spec_location(vm, machine_spec)
|
138
|
-
|
139
|
-
action_handler.performed_action(machine_msg(
|
140
|
-
machine_spec.name,
|
141
|
-
vm.config.instanceUuid,
|
142
|
-
'created'
|
143
|
-
))
|
144
|
-
vm
|
145
|
-
end
|
146
|
-
|
147
|
-
def merge_options!(machine_options)
|
148
|
-
@config = Cheffish::MergedConfig.new(
|
149
|
-
{ machine_options: machine_options },
|
150
|
-
@config
|
151
|
-
)
|
152
|
-
end
|
153
|
-
|
154
|
-
def add_machine_spec_location(vm, machine_spec)
|
155
|
-
machine_spec.location = {
|
156
|
-
'driver_url' => driver_url,
|
157
|
-
'driver_version' => VERSION,
|
158
|
-
'server_id' => vm.config.instanceUuid,
|
159
|
-
'is_windows' => is_windows?(vm),
|
160
|
-
'allocated_at' => Time.now.utc.to_s,
|
161
|
-
'ipaddress' => vm.guest.ipAddress
|
162
|
-
}
|
163
|
-
end
|
164
|
-
|
165
|
-
def find_or_create_vm(bootstrap_options, machine_spec, action_handler)
|
166
|
-
vm = vsphere_helper.find_vm(
|
167
|
-
bootstrap_options[:vm_folder],
|
168
|
-
machine_spec.name
|
169
|
-
)
|
170
|
-
if vm
|
171
|
-
Chef::Log.info machine_msg(
|
172
|
-
machine_spec.name,
|
173
|
-
vm.config.instanceUuid,
|
174
|
-
'already created'
|
175
|
-
)
|
176
|
-
else
|
177
|
-
vm = clone_vm(
|
178
|
-
action_handler,
|
179
|
-
bootstrap_options,
|
180
|
-
machine_spec.name
|
181
|
-
)
|
182
|
-
end
|
183
|
-
vm
|
184
|
-
end
|
185
|
-
|
186
|
-
def full_description(machine_spec, bootstrap_options)
|
187
|
-
description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
|
188
|
-
bootstrap_options.to_hash.each_pair do |key,value|
|
189
|
-
description << " #{key}: #{value.inspect}"
|
190
|
-
end
|
191
|
-
description
|
192
|
-
end
|
193
|
-
|
194
|
-
def machine_msg(name, id, action)
|
195
|
-
"Machine - #{action} - #{name} (#{id} on #{driver_url})"
|
196
|
-
end
|
197
|
-
|
198
|
-
def ready_machine(action_handler, machine_spec, machine_options)
|
199
|
-
merge_options! machine_options
|
200
|
-
|
201
|
-
vm = start_machine(action_handler, machine_spec, machine_options)
|
202
|
-
if vm.nil?
|
203
|
-
raise "Machine #{machine_spec.name} does not have a server "\
|
204
|
-
'associated with it, or server does not exist.'
|
205
|
-
end
|
206
|
-
|
207
|
-
bootstrap_options = machine_options[:bootstrap_options]
|
208
|
-
|
209
|
-
transport_respond?(
|
210
|
-
machine_options,
|
211
|
-
vm,
|
212
|
-
action_handler,
|
213
|
-
machine_spec
|
214
|
-
)
|
215
|
-
|
216
|
-
machine = machine_for(machine_spec,machine_options)
|
217
|
-
|
218
|
-
setup_extra_nics(action_handler, bootstrap_options, vm, machine)
|
219
|
-
|
220
|
-
if has_static_ip(bootstrap_options) && !is_windows?(vm)
|
221
|
-
setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
222
|
-
end
|
223
|
-
|
224
|
-
machine
|
225
|
-
end
|
226
|
-
|
227
|
-
def setup_extra_nics(action_handler, bootstrap_options, vm, machine)
|
228
|
-
networks=bootstrap_options[:network_name]
|
229
|
-
if networks.kind_of?(String)
|
230
|
-
networks=[networks]
|
231
|
-
end
|
232
|
-
return if networks.nil? || networks.count < 2
|
233
|
-
|
234
|
-
new_nics = vsphere_helper.add_extra_nic(
|
235
|
-
action_handler,
|
236
|
-
vm_template_for(bootstrap_options),
|
237
|
-
bootstrap_options,
|
238
|
-
vm
|
239
|
-
)
|
240
|
-
if is_windows?(vm) && !new_nics.nil?
|
241
|
-
new_nics.each do |nic|
|
242
|
-
nic_label = nic.device.deviceInfo.label
|
243
|
-
machine.execute_always(
|
244
|
-
"Disable-Netadapter -Name '#{nic_label}' -Confirm:$false")
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def transport_respond?(
|
250
|
-
machine_options,
|
251
|
-
vm,
|
252
|
-
action_handler,
|
253
|
-
machine_spec
|
254
|
-
)
|
255
|
-
bootstrap_options = machine_options[:bootstrap_options]
|
256
|
-
|
257
|
-
# this waits for vmware tools to start and the vm to presebnt an ip
|
258
|
-
# This may just be the ip of a newly cloned machine
|
259
|
-
# Customization below may change this to a valid ip
|
260
|
-
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
261
|
-
|
262
|
-
if !machine_spec.location['ipaddress'] || !has_ip?(machine_spec.location['ipaddress'], vm)
|
263
|
-
# find the ip we actually want
|
264
|
-
# this will be the static ip to assign
|
265
|
-
# or the ip reported back by the vm if using dhcp
|
266
|
-
# it *may* be nil if just cloned
|
267
|
-
vm_ip = ip_to_bootstrap(bootstrap_options, vm)
|
268
|
-
transport = nil
|
269
|
-
unless vm_ip.nil?
|
270
|
-
transport = transport_for(machine_spec, bootstrap_options[:ssh], vm_ip)
|
271
|
-
end
|
272
|
-
|
273
|
-
unless !transport.nil? && transport.available? && has_ip?(vm_ip, vm)
|
274
|
-
attempt_ip(machine_options, action_handler, vm, machine_spec)
|
275
|
-
end
|
276
|
-
machine_spec.location['ipaddress'] = vm.guest.ipAddress
|
277
|
-
action_handler.report_progress(
|
278
|
-
"IP address obtained: #{machine_spec.location['ipaddress']}")
|
279
|
-
end
|
280
|
-
|
281
|
-
wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
|
282
|
-
|
283
|
-
begin
|
284
|
-
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
285
|
-
rescue Timeout::Error
|
286
|
-
# Only ever reboot once, and only if it's been less than 10 minutes
|
287
|
-
# since we stopped waiting
|
288
|
-
if machine_spec.location['started_at'] ||
|
289
|
-
remaining_wait_time(machine_spec, machine_options) < -(10*60)
|
290
|
-
raise
|
291
|
-
else
|
292
|
-
Chef::Log.warn(machine_msg(
|
293
|
-
machine_spec.name,
|
294
|
-
vm.config.instanceUuid,
|
295
|
-
'started but SSH did not come up. Rebooting...'
|
296
|
-
))
|
297
|
-
restart_server(action_handler, machine_spec, machine_options)
|
298
|
-
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
299
|
-
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
def attempt_ip(machine_options, action_handler, vm, machine_spec)
|
305
|
-
vm_ip = ip_to_bootstrap(machine_options[:bootstrap_options], vm)
|
306
|
-
|
307
|
-
wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
308
|
-
|
309
|
-
unless has_ip?(vm_ip, vm)
|
310
|
-
action_handler.report_progress "rebooting..."
|
311
|
-
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
312
|
-
msg = 'tools have stopped. current power state is '
|
313
|
-
msg << vm.runtime.powerState
|
314
|
-
msg << ' and tools state is '
|
315
|
-
msg << vm.guest.toolsRunningStatus
|
316
|
-
msg << '. powering up server...'
|
317
|
-
action_handler.report_progress(msg.join)
|
318
|
-
vsphere_helper.start_vm(vm)
|
319
|
-
else
|
320
|
-
restart_server(action_handler, machine_spec, machine_options)
|
321
|
-
end
|
322
|
-
wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
def wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
|
327
|
-
return unless bootstrap_options[:customization_spec]
|
328
|
-
return unless bootstrap_options[:customization_spec][:domain]
|
329
|
-
|
330
|
-
domain = bootstrap_options[:customization_spec][:domain]
|
331
|
-
if is_windows?(vm) && domain != 'local'
|
332
|
-
start = Time.now.utc
|
333
|
-
trimmed_name = machine_spec.name.byteslice(0,15)
|
334
|
-
expected_name="#{trimmed_name}.#{domain}"
|
335
|
-
action_handler.report_progress(
|
336
|
-
"waiting to domain join and be named #{expected_name}")
|
337
|
-
until (Time.now.utc - start) > 30 ||
|
338
|
-
(vm.guest.hostName == expected_name) do
|
339
|
-
print '.'
|
340
|
-
sleep 5
|
341
|
-
end
|
342
|
-
end
|
343
|
-
end
|
344
|
-
|
345
|
-
def wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
346
|
-
bootstrap_options = machine_options[:bootstrap_options]
|
347
|
-
vm_ip = ip_to_bootstrap(bootstrap_options, vm)
|
348
|
-
ready_timeout = machine_options[:ready_timeout] || 300
|
349
|
-
msg = "waiting up to #{ready_timeout} seconds for customization"
|
350
|
-
msg << " and find #{vm_ip}" unless vm_ip == vm.guest.ipAddress
|
351
|
-
action_handler.report_progress msg
|
352
|
-
|
353
|
-
start = Time.now.utc
|
354
|
-
connectable = false
|
355
|
-
until (Time.now.utc - start) > ready_timeout || connectable do
|
356
|
-
action_handler.report_progress(
|
357
|
-
"IP addresses found: #{all_ips_for(vm)}")
|
358
|
-
vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
|
359
|
-
if has_ip?(vm_ip, vm)
|
360
|
-
connectable = transport_for(
|
361
|
-
machine_spec,
|
362
|
-
machine_options[:bootstrap_options][:ssh],
|
363
|
-
vm_ip
|
364
|
-
).available?
|
365
|
-
end
|
366
|
-
sleep 5
|
367
|
-
end
|
368
|
-
end
|
369
|
-
|
370
|
-
def all_ips_for(vm)
|
371
|
-
vm.guest.net.map { |net| net.ipAddress}.flatten
|
372
|
-
end
|
373
|
-
|
374
|
-
def has_ip?(ip, vm)
|
375
|
-
all_ips_for(vm).include?(ip)
|
376
|
-
end
|
377
|
-
|
378
|
-
# Connect to machine without acquiring it
|
379
|
-
def connect_to_machine(machine_spec, machine_options)
|
380
|
-
merge_options! machine_options
|
381
|
-
machine_for(machine_spec, machine_options)
|
382
|
-
end
|
383
|
-
|
384
|
-
def destroy_machine(action_handler, machine_spec, machine_options)
|
385
|
-
merge_options! machine_options
|
386
|
-
vm = vm_for(machine_spec)
|
387
|
-
if vm
|
388
|
-
action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
|
389
|
-
begin
|
390
|
-
vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
|
391
|
-
vm.Destroy_Task.wait_for_completion
|
392
|
-
rescue RbVmomi::Fault => fault
|
393
|
-
raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
|
394
|
-
ensure
|
395
|
-
machine_spec.location = nil
|
396
|
-
end
|
397
|
-
end
|
398
|
-
end
|
399
|
-
strategy = convergence_strategy_for(machine_spec, machine_options)
|
400
|
-
strategy.cleanup_convergence(action_handler, machine_spec)
|
401
|
-
end
|
402
|
-
|
403
|
-
def stop_machine(action_handler, machine_spec, machine_options)
|
404
|
-
merge_options! machine_options
|
405
|
-
vm = vm_for(machine_spec)
|
406
|
-
if vm
|
407
|
-
action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
|
408
|
-
vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
|
409
|
-
end
|
410
|
-
end
|
411
|
-
end
|
412
|
-
|
413
|
-
def start_machine(action_handler, machine_spec, machine_options)
|
414
|
-
merge_options! machine_options
|
415
|
-
vm = vm_for(machine_spec)
|
416
|
-
if vm
|
417
|
-
action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
|
418
|
-
vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
|
419
|
-
end
|
420
|
-
end
|
421
|
-
vm
|
422
|
-
end
|
423
|
-
|
424
|
-
def restart_server(action_handler, machine_spec, machine_options)
|
425
|
-
action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
|
426
|
-
stop_machine(action_handler, machine_spec, machine_options)
|
427
|
-
start_machine(action_handler, machine_spec, machine_options)
|
428
|
-
machine_spec.location['started_at'] = Time.now.utc.to_s
|
429
|
-
end
|
430
|
-
end
|
431
|
-
|
432
|
-
protected
|
433
|
-
|
434
|
-
def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
435
|
-
host_lookup = machine.execute_always('host google.com')
|
436
|
-
if host_lookup.exitstatus != 0
|
437
|
-
if host_lookup.stdout.include?("setlocale: LC_ALL")
|
438
|
-
machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
|
439
|
-
end
|
440
|
-
distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
|
441
|
-
Chef::Log.info "Found distro:#{distro}"
|
442
|
-
if distro == 'Ubuntu'
|
443
|
-
distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
|
444
|
-
Chef::Log.info "Found distro version:#{distro_version}"
|
445
|
-
if distro_version>= 12.04
|
446
|
-
Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
|
447
|
-
interfaces_file = "/etc/network/interfaces"
|
448
|
-
nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
|
449
|
-
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
|
450
|
-
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
|
451
|
-
machine.execute_always('/etc/init.d/networking restart')
|
452
|
-
machine.execute_always('apt-get -qq update')
|
453
|
-
end
|
454
|
-
end
|
455
|
-
end
|
456
|
-
end
|
457
|
-
|
458
|
-
def has_static_ip(bootstrap_options)
|
459
|
-
if bootstrap_options.has_key?(:customization_spec)
|
460
|
-
bootstrap_options = bootstrap_options[:customization_spec]
|
461
|
-
if bootstrap_options.has_key?(:ipsettings)
|
462
|
-
bootstrap_options = bootstrap_options[:ipsettings]
|
463
|
-
if bootstrap_options.has_key?(:ip)
|
464
|
-
return true
|
465
|
-
end
|
466
|
-
end
|
467
|
-
end
|
468
|
-
false
|
469
|
-
end
|
470
|
-
|
471
|
-
def remaining_wait_time(machine_spec, machine_options)
|
472
|
-
if machine_spec.location['started_at']
|
473
|
-
(machine_options[:start_timeout] || 600) -
|
474
|
-
(Time.now.utc - Time.parse(machine_spec.location['started_at']))
|
475
|
-
else
|
476
|
-
(machine_options[:create_timeout] || 600) -
|
477
|
-
(Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
|
478
|
-
end
|
479
|
-
end
|
480
|
-
|
481
|
-
def wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
482
|
-
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
483
|
-
if action_handler.should_perform_actions
|
484
|
-
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
|
485
|
-
until remaining_wait_time(machine_spec, machine_options) < 0 ||
|
486
|
-
(vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
|
487
|
-
print "."
|
488
|
-
sleep 5
|
489
|
-
end
|
490
|
-
action_handler.report_progress "#{machine_spec.name} is now ready"
|
491
|
-
end
|
492
|
-
end
|
493
|
-
end
|
494
|
-
|
495
|
-
def vm_for(machine_spec)
|
496
|
-
if machine_spec.location
|
497
|
-
vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
|
498
|
-
else
|
499
|
-
nil
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
def clone_vm(action_handler, bootstrap_options, machine_name)
|
504
|
-
vm_template = vm_template_for(bootstrap_options)
|
505
|
-
|
506
|
-
spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
|
507
|
-
clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
|
508
|
-
Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
|
509
|
-
|
510
|
-
vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
|
511
|
-
vm_template.CloneVM_Task(
|
512
|
-
name: machine_name,
|
513
|
-
folder: vm_folder,
|
514
|
-
spec: clone_spec
|
515
|
-
).wait_for_completion
|
516
|
-
|
517
|
-
vm = vsphere_helper.find_vm(vm_folder, machine_name)
|
518
|
-
|
519
|
-
additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
|
520
|
-
if !additional_disk_size_gb.is_a?(Array)
|
521
|
-
additional_disk_size_gb = [additional_disk_size_gb]
|
522
|
-
end
|
523
|
-
|
524
|
-
additional_disk_size_gb.each do |size|
|
525
|
-
size = size.to_i
|
526
|
-
next if size == 0
|
527
|
-
if bootstrap_options[:datastore].to_s.empty?
|
528
|
-
raise ':datastore must be specified when adding a disk to a cloned vm'
|
529
|
-
end
|
530
|
-
task = vm.ReconfigVM_Task(
|
531
|
-
spec: RbVmomi::VIM.VirtualMachineConfigSpec(
|
532
|
-
deviceChange: [
|
533
|
-
vsphere_helper.virtual_disk_for(
|
534
|
-
vm,
|
535
|
-
bootstrap_options[:datastore],
|
536
|
-
size
|
537
|
-
)
|
538
|
-
]
|
539
|
-
)
|
540
|
-
)
|
541
|
-
task.wait_for_completion
|
542
|
-
end
|
543
|
-
|
544
|
-
vm
|
545
|
-
end
|
546
|
-
|
547
|
-
def vsphere_helper
|
548
|
-
@vsphere_helper ||= VsphereHelper.new(
|
549
|
-
connect_options,
|
550
|
-
config[:machine_options][:bootstrap_options][:datacenter]
|
551
|
-
)
|
552
|
-
end
|
553
|
-
|
554
|
-
def vm_template_for(bootstrap_options)
|
555
|
-
template_folder = bootstrap_options[:template_folder]
|
556
|
-
template_name = bootstrap_options[:template_name]
|
557
|
-
vsphere_helper.find_vm(template_folder, template_name) ||
|
558
|
-
raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
|
559
|
-
end
|
560
|
-
|
561
|
-
def machine_for(machine_spec, machine_options)
|
562
|
-
if machine_spec.location.nil?
|
563
|
-
raise "Server for node #{machine_spec.name} has not been created!"
|
564
|
-
end
|
565
|
-
|
566
|
-
transport = transport_for(
|
567
|
-
machine_spec,
|
568
|
-
machine_options[:bootstrap_options][:ssh]
|
569
|
-
)
|
570
|
-
strategy = convergence_strategy_for(machine_spec, machine_options)
|
571
|
-
|
572
|
-
if machine_spec.location['is_windows']
|
573
|
-
Chef::Provisioning::Machine::WindowsMachine.new(
|
574
|
-
machine_spec, transport, strategy)
|
575
|
-
else
|
576
|
-
Chef::Provisioning::Machine::UnixMachine.new(
|
577
|
-
machine_spec, transport, strategy)
|
578
|
-
end
|
579
|
-
end
|
580
|
-
|
581
|
-
def is_windows?(vm)
|
582
|
-
return false if vm.nil?
|
583
|
-
vm.config.guestId.start_with?('win')
|
584
|
-
end
|
585
|
-
|
586
|
-
def convergence_strategy_for(machine_spec, machine_options)
|
587
|
-
require 'chef/provisioning/convergence_strategy/install_msi'
|
588
|
-
require 'chef/provisioning/convergence_strategy/install_cached'
|
589
|
-
require 'chef/provisioning/convergence_strategy/no_converge'
|
590
|
-
|
591
|
-
mopts = machine_options[:convergence_options].to_hash.dup
|
592
|
-
if mopts[:chef_server]
|
593
|
-
mopts[:chef_server] = mopts[:chef_server].to_hash.dup
|
594
|
-
mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
|
595
|
-
end
|
596
|
-
|
597
|
-
if !machine_spec.location
|
598
|
-
return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
|
599
|
-
mopts, config)
|
600
|
-
end
|
601
|
-
|
602
|
-
if machine_spec.location['is_windows']
|
603
|
-
Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
|
604
|
-
mopts, config)
|
605
|
-
else
|
606
|
-
Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
|
607
|
-
mopts, config)
|
608
|
-
end
|
609
|
-
end
|
610
|
-
|
611
|
-
def wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
612
|
-
transport = transport_for(
|
613
|
-
machine_spec,
|
614
|
-
machine_options[:bootstrap_options][:ssh]
|
615
|
-
)
|
616
|
-
if !transport.available?
|
617
|
-
if action_handler.should_perform_actions
|
618
|
-
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
|
619
|
-
|
620
|
-
until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
|
621
|
-
print "."
|
622
|
-
sleep 5
|
623
|
-
end
|
624
|
-
|
625
|
-
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
626
|
-
end
|
627
|
-
end
|
628
|
-
end
|
629
|
-
|
630
|
-
def transport_for(
|
631
|
-
machine_spec,
|
632
|
-
remoting_options,
|
633
|
-
ip = machine_spec.location['ipaddress']
|
634
|
-
)
|
635
|
-
if machine_spec.location['is_windows']
|
636
|
-
create_winrm_transport(ip, remoting_options)
|
637
|
-
else
|
638
|
-
create_ssh_transport(ip, remoting_options)
|
639
|
-
end
|
640
|
-
end
|
641
|
-
|
642
|
-
def create_winrm_transport(host, options)
|
643
|
-
require 'chef/provisioning/transport/winrm'
|
644
|
-
opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
|
645
|
-
winrm_options = {
|
646
|
-
user: "#{options[:user]}",
|
647
|
-
pass: options[:password],
|
648
|
-
opt => true
|
649
|
-
}
|
650
|
-
|
651
|
-
Chef::Provisioning::Transport::WinRM.new(
|
652
|
-
"http://#{host}:5985/wsman",
|
653
|
-
:plaintext,
|
654
|
-
winrm_options,
|
655
|
-
config
|
656
|
-
)
|
657
|
-
end
|
658
|
-
|
659
|
-
def create_ssh_transport(host, options)
|
660
|
-
require 'chef/provisioning/transport/ssh'
|
661
|
-
ssh_user = options[:user]
|
662
|
-
Chef::Provisioning::Transport::SSH.new(
|
663
|
-
host,
|
664
|
-
ssh_user,
|
665
|
-
options,
|
666
|
-
@config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
|
667
|
-
config
|
668
|
-
)
|
669
|
-
end
|
670
|
-
|
671
|
-
def ip_to_bootstrap(bootstrap_options, vm)
|
672
|
-
if has_static_ip(bootstrap_options)
|
673
|
-
bootstrap_options[:customization_spec][:ipsettings][:ip]
|
674
|
-
else
|
675
|
-
vm.guest.ipAddress
|
676
|
-
end
|
677
|
-
end
|
678
|
-
end
|
679
|
-
end
|
1
|
+
require 'chef'
|
2
|
+
require 'cheffish/merged_config'
|
3
|
+
require 'chef/provisioning/driver'
|
4
|
+
require 'chef/provisioning/machine/windows_machine'
|
5
|
+
require 'chef/provisioning/machine/unix_machine'
|
6
|
+
require 'chef/provisioning/vsphere_driver/clone_spec_builder'
|
7
|
+
require 'chef/provisioning/vsphere_driver/version'
|
8
|
+
require 'chef/provisioning/vsphere_driver/vsphere_helpers'
|
9
|
+
require 'chef/provisioning/vsphere_driver/vsphere_url'
|
10
|
+
|
11
|
+
module ChefProvisioningVsphere
|
12
|
+
# Provisions machines in vSphere.
|
13
|
+
class VsphereDriver < Chef::Provisioning::Driver
|
14
|
+
include Chef::Mixin::ShellOut
|
15
|
+
|
16
|
+
def self.from_url(driver_url, config)
|
17
|
+
VsphereDriver.new(driver_url, config)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Create a new Vsphere provisioner.
|
21
|
+
#
|
22
|
+
# ## Parameters
|
23
|
+
# connect_options - hash of options to be passed to RbVmomi::VIM.connect
|
24
|
+
# :host - required - hostname of the vSphere API server
|
25
|
+
# :port - optional - port on the vSphere API server (default: 443)
|
26
|
+
# :path - optional - path on the vSphere API server (default: /sdk)
|
27
|
+
# :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
|
28
|
+
# :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
|
29
|
+
# :user - required - user name to use in connection to vSphere API server
|
30
|
+
# :password - required - password to use in connection to vSphere API server
|
31
|
+
def self.canonicalize_url(driver_url, config)
|
32
|
+
config = symbolize_keys(config)
|
33
|
+
[ driver_url || URI::VsphereUrl.from_config(config).to_s, config ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.symbolize_keys(h)
|
37
|
+
Hash === h ?
|
38
|
+
Hash[
|
39
|
+
h.map do |k, v|
|
40
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
|
41
|
+
end
|
42
|
+
] : h
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(driver_url, config)
|
46
|
+
super(driver_url, config)
|
47
|
+
|
48
|
+
uri = URI(driver_url)
|
49
|
+
@connect_options = {
|
50
|
+
provider: 'vsphere',
|
51
|
+
host: uri.host,
|
52
|
+
port: uri.port,
|
53
|
+
use_ssl: uri.use_ssl,
|
54
|
+
insecure: uri.insecure,
|
55
|
+
path: uri.path
|
56
|
+
}
|
57
|
+
|
58
|
+
if driver_options
|
59
|
+
@connect_options[:user] = driver_options[:user]
|
60
|
+
@connect_options[:password] = driver_options[:password]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_reader :connect_options
|
65
|
+
|
66
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
67
|
+
# object pointing at the machine, allowing useful actions like setup,
|
68
|
+
# converge, execute, file and directory. The Machine object will have a
|
69
|
+
# "node" property which must be saved to the server (if it is any
|
70
|
+
# different from the original node object).
|
71
|
+
#
|
72
|
+
# ## Parameters
|
73
|
+
# action_handler - the action_handler object that is calling this method; this
|
74
|
+
# is generally a action_handler, but could be anything that can support the
|
75
|
+
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
76
|
+
# kitchen metal driver for acquiring and destroying VMs; see the base
|
77
|
+
# class for what needs providing).
|
78
|
+
# node - node object (deserialized json) representing this machine. If
|
79
|
+
# the node has a provisioner_options hash in it, these will be used
|
80
|
+
# instead of options provided by the provisioner. TODO compare and
|
81
|
+
# fail if different?
|
82
|
+
# node will have node['normal']['provisioner_options'] in it with any options.
|
83
|
+
# It is a hash with this format:
|
84
|
+
#
|
85
|
+
# -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
|
86
|
+
# -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
|
87
|
+
# :datacenter
|
88
|
+
# :resource_pool
|
89
|
+
# :cluster
|
90
|
+
# :datastore
|
91
|
+
# :template_name
|
92
|
+
# :template_folder
|
93
|
+
# :vm_folder
|
94
|
+
# :winrm {...} (not yet implemented)
|
95
|
+
# :ssh {...}
|
96
|
+
#
|
97
|
+
# Example bootstrap_options for vSphere:
|
98
|
+
# TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
|
99
|
+
# 'bootstrap_options' => {
|
100
|
+
# 'template_name' =>'centos6.small',
|
101
|
+
# 'template_folder' =>'Templates',
|
102
|
+
# 'vm_folder' => 'MyApp'
|
103
|
+
# }
|
104
|
+
#
|
105
|
+
# node['normal']['provisioner_output'] will be populated with information
|
106
|
+
# about the created machine. For vSphere, it is a hash with this
|
107
|
+
# format:
|
108
|
+
#
|
109
|
+
# -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
|
110
|
+
# -- vm_folder: name of the vSphere folder containing the VM
|
111
|
+
#
|
112
|
+
def allocate_machine(action_handler, machine_spec, machine_options)
|
113
|
+
merge_options! machine_options
|
114
|
+
|
115
|
+
if machine_spec.location
|
116
|
+
Chef::Log.warn(
|
117
|
+
"Checking to see if #{machine_spec.location} has been created...")
|
118
|
+
vm = vm_for(machine_spec)
|
119
|
+
if vm
|
120
|
+
Chef::Log.warn 'returning existing machine'
|
121
|
+
return vm
|
122
|
+
else
|
123
|
+
Chef::Log.warn machine_msg(
|
124
|
+
machine_spec.name,
|
125
|
+
machine_spec.location['server_id'],
|
126
|
+
'no longer exists. Recreating ...'
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
bootstrap_options = machine_options[:bootstrap_options]
|
131
|
+
|
132
|
+
action_handler.report_progress full_description(
|
133
|
+
machine_spec, bootstrap_options)
|
134
|
+
|
135
|
+
vm = find_or_create_vm(bootstrap_options, machine_spec, action_handler)
|
136
|
+
|
137
|
+
add_machine_spec_location(vm, machine_spec)
|
138
|
+
|
139
|
+
action_handler.performed_action(machine_msg(
|
140
|
+
machine_spec.name,
|
141
|
+
vm.config.instanceUuid,
|
142
|
+
'created'
|
143
|
+
))
|
144
|
+
vm
|
145
|
+
end
|
146
|
+
|
147
|
+
def merge_options!(machine_options)
|
148
|
+
@config = Cheffish::MergedConfig.new(
|
149
|
+
{ machine_options: machine_options },
|
150
|
+
@config
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
def add_machine_spec_location(vm, machine_spec)
|
155
|
+
machine_spec.location = {
|
156
|
+
'driver_url' => driver_url,
|
157
|
+
'driver_version' => VERSION,
|
158
|
+
'server_id' => vm.config.instanceUuid,
|
159
|
+
'is_windows' => is_windows?(vm),
|
160
|
+
'allocated_at' => Time.now.utc.to_s,
|
161
|
+
'ipaddress' => vm.guest.ipAddress
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_or_create_vm(bootstrap_options, machine_spec, action_handler)
|
166
|
+
vm = vsphere_helper.find_vm(
|
167
|
+
bootstrap_options[:vm_folder],
|
168
|
+
machine_spec.name
|
169
|
+
)
|
170
|
+
if vm
|
171
|
+
Chef::Log.info machine_msg(
|
172
|
+
machine_spec.name,
|
173
|
+
vm.config.instanceUuid,
|
174
|
+
'already created'
|
175
|
+
)
|
176
|
+
else
|
177
|
+
vm = clone_vm(
|
178
|
+
action_handler,
|
179
|
+
bootstrap_options,
|
180
|
+
machine_spec.name
|
181
|
+
)
|
182
|
+
end
|
183
|
+
vm
|
184
|
+
end
|
185
|
+
|
186
|
+
def full_description(machine_spec, bootstrap_options)
|
187
|
+
description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
|
188
|
+
bootstrap_options.to_hash.each_pair do |key,value|
|
189
|
+
description << " #{key}: #{value.inspect}"
|
190
|
+
end
|
191
|
+
description
|
192
|
+
end
|
193
|
+
|
194
|
+
def machine_msg(name, id, action)
|
195
|
+
"Machine - #{action} - #{name} (#{id} on #{driver_url})"
|
196
|
+
end
|
197
|
+
|
198
|
+
def ready_machine(action_handler, machine_spec, machine_options)
|
199
|
+
merge_options! machine_options
|
200
|
+
|
201
|
+
vm = start_machine(action_handler, machine_spec, machine_options)
|
202
|
+
if vm.nil?
|
203
|
+
raise "Machine #{machine_spec.name} does not have a server "\
|
204
|
+
'associated with it, or server does not exist.'
|
205
|
+
end
|
206
|
+
|
207
|
+
bootstrap_options = machine_options[:bootstrap_options]
|
208
|
+
|
209
|
+
transport_respond?(
|
210
|
+
machine_options,
|
211
|
+
vm,
|
212
|
+
action_handler,
|
213
|
+
machine_spec
|
214
|
+
)
|
215
|
+
|
216
|
+
machine = machine_for(machine_spec,machine_options)
|
217
|
+
|
218
|
+
setup_extra_nics(action_handler, bootstrap_options, vm, machine)
|
219
|
+
|
220
|
+
if has_static_ip(bootstrap_options) && !is_windows?(vm)
|
221
|
+
setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
222
|
+
end
|
223
|
+
|
224
|
+
machine
|
225
|
+
end
|
226
|
+
|
227
|
+
def setup_extra_nics(action_handler, bootstrap_options, vm, machine)
|
228
|
+
networks=bootstrap_options[:network_name]
|
229
|
+
if networks.kind_of?(String)
|
230
|
+
networks=[networks]
|
231
|
+
end
|
232
|
+
return if networks.nil? || networks.count < 2
|
233
|
+
|
234
|
+
new_nics = vsphere_helper.add_extra_nic(
|
235
|
+
action_handler,
|
236
|
+
vm_template_for(bootstrap_options),
|
237
|
+
bootstrap_options,
|
238
|
+
vm
|
239
|
+
)
|
240
|
+
if is_windows?(vm) && !new_nics.nil?
|
241
|
+
new_nics.each do |nic|
|
242
|
+
nic_label = nic.device.deviceInfo.label
|
243
|
+
machine.execute_always(
|
244
|
+
"Disable-Netadapter -Name '#{nic_label}' -Confirm:$false")
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def transport_respond?(
|
250
|
+
machine_options,
|
251
|
+
vm,
|
252
|
+
action_handler,
|
253
|
+
machine_spec
|
254
|
+
)
|
255
|
+
bootstrap_options = machine_options[:bootstrap_options]
|
256
|
+
|
257
|
+
# this waits for vmware tools to start and the vm to presebnt an ip
|
258
|
+
# This may just be the ip of a newly cloned machine
|
259
|
+
# Customization below may change this to a valid ip
|
260
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
261
|
+
|
262
|
+
if !machine_spec.location['ipaddress'] || !has_ip?(machine_spec.location['ipaddress'], vm)
|
263
|
+
# find the ip we actually want
|
264
|
+
# this will be the static ip to assign
|
265
|
+
# or the ip reported back by the vm if using dhcp
|
266
|
+
# it *may* be nil if just cloned
|
267
|
+
vm_ip = ip_to_bootstrap(bootstrap_options, vm)
|
268
|
+
transport = nil
|
269
|
+
unless vm_ip.nil?
|
270
|
+
transport = transport_for(machine_spec, bootstrap_options[:ssh], vm_ip)
|
271
|
+
end
|
272
|
+
|
273
|
+
unless !transport.nil? && transport.available? && has_ip?(vm_ip, vm)
|
274
|
+
attempt_ip(machine_options, action_handler, vm, machine_spec)
|
275
|
+
end
|
276
|
+
machine_spec.location['ipaddress'] = vm.guest.ipAddress
|
277
|
+
action_handler.report_progress(
|
278
|
+
"IP address obtained: #{machine_spec.location['ipaddress']}")
|
279
|
+
end
|
280
|
+
|
281
|
+
wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
|
282
|
+
|
283
|
+
begin
|
284
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
285
|
+
rescue Timeout::Error
|
286
|
+
# Only ever reboot once, and only if it's been less than 10 minutes
|
287
|
+
# since we stopped waiting
|
288
|
+
if machine_spec.location['started_at'] ||
|
289
|
+
remaining_wait_time(machine_spec, machine_options) < -(10*60)
|
290
|
+
raise
|
291
|
+
else
|
292
|
+
Chef::Log.warn(machine_msg(
|
293
|
+
machine_spec.name,
|
294
|
+
vm.config.instanceUuid,
|
295
|
+
'started but SSH did not come up. Rebooting...'
|
296
|
+
))
|
297
|
+
restart_server(action_handler, machine_spec, machine_options)
|
298
|
+
wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
299
|
+
wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def attempt_ip(machine_options, action_handler, vm, machine_spec)
|
305
|
+
vm_ip = ip_to_bootstrap(machine_options[:bootstrap_options], vm)
|
306
|
+
|
307
|
+
wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
308
|
+
|
309
|
+
unless has_ip?(vm_ip, vm)
|
310
|
+
action_handler.report_progress "rebooting..."
|
311
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
312
|
+
msg = 'tools have stopped. current power state is '
|
313
|
+
msg << vm.runtime.powerState
|
314
|
+
msg << ' and tools state is '
|
315
|
+
msg << vm.guest.toolsRunningStatus
|
316
|
+
msg << '. powering up server...'
|
317
|
+
action_handler.report_progress(msg.join)
|
318
|
+
vsphere_helper.start_vm(vm)
|
319
|
+
else
|
320
|
+
restart_server(action_handler, machine_spec, machine_options)
|
321
|
+
end
|
322
|
+
wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
|
327
|
+
return unless bootstrap_options[:customization_spec]
|
328
|
+
return unless bootstrap_options[:customization_spec][:domain]
|
329
|
+
|
330
|
+
domain = bootstrap_options[:customization_spec][:domain]
|
331
|
+
if is_windows?(vm) && domain != 'local'
|
332
|
+
start = Time.now.utc
|
333
|
+
trimmed_name = machine_spec.name.byteslice(0,15)
|
334
|
+
expected_name="#{trimmed_name}.#{domain}"
|
335
|
+
action_handler.report_progress(
|
336
|
+
"waiting to domain join and be named #{expected_name}")
|
337
|
+
until (Time.now.utc - start) > 30 ||
|
338
|
+
(vm.guest.hostName == expected_name) do
|
339
|
+
print '.'
|
340
|
+
sleep 5
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
def wait_for_ip(vm, machine_options, machine_spec, action_handler)
|
346
|
+
bootstrap_options = machine_options[:bootstrap_options]
|
347
|
+
vm_ip = ip_to_bootstrap(bootstrap_options, vm)
|
348
|
+
ready_timeout = machine_options[:ready_timeout] || 300
|
349
|
+
msg = "waiting up to #{ready_timeout} seconds for customization"
|
350
|
+
msg << " and find #{vm_ip}" unless vm_ip == vm.guest.ipAddress
|
351
|
+
action_handler.report_progress msg
|
352
|
+
|
353
|
+
start = Time.now.utc
|
354
|
+
connectable = false
|
355
|
+
until (Time.now.utc - start) > ready_timeout || connectable do
|
356
|
+
action_handler.report_progress(
|
357
|
+
"IP addresses found: #{all_ips_for(vm)}")
|
358
|
+
vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
|
359
|
+
if has_ip?(vm_ip, vm)
|
360
|
+
connectable = transport_for(
|
361
|
+
machine_spec,
|
362
|
+
machine_options[:bootstrap_options][:ssh],
|
363
|
+
vm_ip
|
364
|
+
).available?
|
365
|
+
end
|
366
|
+
sleep 5
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def all_ips_for(vm)
|
371
|
+
vm.guest.net.map { |net| net.ipAddress}.flatten
|
372
|
+
end
|
373
|
+
|
374
|
+
def has_ip?(ip, vm)
|
375
|
+
all_ips_for(vm).include?(ip)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Connect to machine without acquiring it
|
379
|
+
def connect_to_machine(machine_spec, machine_options)
|
380
|
+
merge_options! machine_options
|
381
|
+
machine_for(machine_spec, machine_options)
|
382
|
+
end
|
383
|
+
|
384
|
+
def destroy_machine(action_handler, machine_spec, machine_options)
|
385
|
+
merge_options! machine_options
|
386
|
+
vm = vm_for(machine_spec)
|
387
|
+
if vm
|
388
|
+
action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
|
389
|
+
begin
|
390
|
+
vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
|
391
|
+
vm.Destroy_Task.wait_for_completion
|
392
|
+
rescue RbVmomi::Fault => fault
|
393
|
+
raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
|
394
|
+
ensure
|
395
|
+
machine_spec.location = nil
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
400
|
+
strategy.cleanup_convergence(action_handler, machine_spec)
|
401
|
+
end
|
402
|
+
|
403
|
+
def stop_machine(action_handler, machine_spec, machine_options)
|
404
|
+
merge_options! machine_options
|
405
|
+
vm = vm_for(machine_spec)
|
406
|
+
if vm
|
407
|
+
action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
|
408
|
+
vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
def start_machine(action_handler, machine_spec, machine_options)
|
414
|
+
merge_options! machine_options
|
415
|
+
vm = vm_for(machine_spec)
|
416
|
+
if vm
|
417
|
+
action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
|
418
|
+
vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
|
419
|
+
end
|
420
|
+
end
|
421
|
+
vm
|
422
|
+
end
|
423
|
+
|
424
|
+
def restart_server(action_handler, machine_spec, machine_options)
|
425
|
+
action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
|
426
|
+
stop_machine(action_handler, machine_spec, machine_options)
|
427
|
+
start_machine(action_handler, machine_spec, machine_options)
|
428
|
+
machine_spec.location['started_at'] = Time.now.utc.to_s
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
protected
|
433
|
+
|
434
|
+
def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
|
435
|
+
host_lookup = machine.execute_always('host google.com')
|
436
|
+
if host_lookup.exitstatus != 0
|
437
|
+
if host_lookup.stdout.include?("setlocale: LC_ALL")
|
438
|
+
machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
|
439
|
+
end
|
440
|
+
distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
|
441
|
+
Chef::Log.info "Found distro:#{distro}"
|
442
|
+
if distro == 'Ubuntu'
|
443
|
+
distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
|
444
|
+
Chef::Log.info "Found distro version:#{distro_version}"
|
445
|
+
if distro_version>= 12.04
|
446
|
+
Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
|
447
|
+
interfaces_file = "/etc/network/interfaces"
|
448
|
+
nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
|
449
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
|
450
|
+
machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
|
451
|
+
machine.execute_always('/etc/init.d/networking restart')
|
452
|
+
machine.execute_always('apt-get -qq update')
|
453
|
+
end
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
def has_static_ip(bootstrap_options)
|
459
|
+
if bootstrap_options.has_key?(:customization_spec)
|
460
|
+
bootstrap_options = bootstrap_options[:customization_spec]
|
461
|
+
if bootstrap_options.has_key?(:ipsettings)
|
462
|
+
bootstrap_options = bootstrap_options[:ipsettings]
|
463
|
+
if bootstrap_options.has_key?(:ip)
|
464
|
+
return true
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
false
|
469
|
+
end
|
470
|
+
|
471
|
+
def remaining_wait_time(machine_spec, machine_options)
|
472
|
+
if machine_spec.location['started_at']
|
473
|
+
(machine_options[:start_timeout] || 600) -
|
474
|
+
(Time.now.utc - Time.parse(machine_spec.location['started_at']))
|
475
|
+
else
|
476
|
+
(machine_options[:create_timeout] || 600) -
|
477
|
+
(Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
def wait_until_ready(action_handler, machine_spec, machine_options, vm)
|
482
|
+
if vm.guest.toolsRunningStatus != "guestToolsRunning"
|
483
|
+
if action_handler.should_perform_actions
|
484
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
|
485
|
+
until remaining_wait_time(machine_spec, machine_options) < 0 ||
|
486
|
+
(vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
|
487
|
+
print "."
|
488
|
+
sleep 5
|
489
|
+
end
|
490
|
+
action_handler.report_progress "#{machine_spec.name} is now ready"
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def vm_for(machine_spec)
|
496
|
+
if machine_spec.location
|
497
|
+
vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
|
498
|
+
else
|
499
|
+
nil
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def clone_vm(action_handler, bootstrap_options, machine_name)
|
504
|
+
vm_template = vm_template_for(bootstrap_options)
|
505
|
+
|
506
|
+
spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
|
507
|
+
clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
|
508
|
+
Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
|
509
|
+
|
510
|
+
vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
|
511
|
+
vm_template.CloneVM_Task(
|
512
|
+
name: machine_name,
|
513
|
+
folder: vm_folder,
|
514
|
+
spec: clone_spec
|
515
|
+
).wait_for_completion
|
516
|
+
|
517
|
+
vm = vsphere_helper.find_vm(vm_folder, machine_name)
|
518
|
+
|
519
|
+
additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
|
520
|
+
if !additional_disk_size_gb.is_a?(Array)
|
521
|
+
additional_disk_size_gb = [additional_disk_size_gb]
|
522
|
+
end
|
523
|
+
|
524
|
+
additional_disk_size_gb.each do |size|
|
525
|
+
size = size.to_i
|
526
|
+
next if size == 0
|
527
|
+
if bootstrap_options[:datastore].to_s.empty?
|
528
|
+
raise ':datastore must be specified when adding a disk to a cloned vm'
|
529
|
+
end
|
530
|
+
task = vm.ReconfigVM_Task(
|
531
|
+
spec: RbVmomi::VIM.VirtualMachineConfigSpec(
|
532
|
+
deviceChange: [
|
533
|
+
vsphere_helper.virtual_disk_for(
|
534
|
+
vm,
|
535
|
+
bootstrap_options[:datastore],
|
536
|
+
size
|
537
|
+
)
|
538
|
+
]
|
539
|
+
)
|
540
|
+
)
|
541
|
+
task.wait_for_completion
|
542
|
+
end
|
543
|
+
|
544
|
+
vm
|
545
|
+
end
|
546
|
+
|
547
|
+
def vsphere_helper
|
548
|
+
@vsphere_helper ||= VsphereHelper.new(
|
549
|
+
connect_options,
|
550
|
+
config[:machine_options][:bootstrap_options][:datacenter]
|
551
|
+
)
|
552
|
+
end
|
553
|
+
|
554
|
+
def vm_template_for(bootstrap_options)
|
555
|
+
template_folder = bootstrap_options[:template_folder]
|
556
|
+
template_name = bootstrap_options[:template_name]
|
557
|
+
vsphere_helper.find_vm(template_folder, template_name) ||
|
558
|
+
raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
|
559
|
+
end
|
560
|
+
|
561
|
+
def machine_for(machine_spec, machine_options)
|
562
|
+
if machine_spec.location.nil?
|
563
|
+
raise "Server for node #{machine_spec.name} has not been created!"
|
564
|
+
end
|
565
|
+
|
566
|
+
transport = transport_for(
|
567
|
+
machine_spec,
|
568
|
+
machine_options[:bootstrap_options][:ssh]
|
569
|
+
)
|
570
|
+
strategy = convergence_strategy_for(machine_spec, machine_options)
|
571
|
+
|
572
|
+
if machine_spec.location['is_windows']
|
573
|
+
Chef::Provisioning::Machine::WindowsMachine.new(
|
574
|
+
machine_spec, transport, strategy)
|
575
|
+
else
|
576
|
+
Chef::Provisioning::Machine::UnixMachine.new(
|
577
|
+
machine_spec, transport, strategy)
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
def is_windows?(vm)
|
582
|
+
return false if vm.nil?
|
583
|
+
vm.config.guestId.start_with?('win')
|
584
|
+
end
|
585
|
+
|
586
|
+
def convergence_strategy_for(machine_spec, machine_options)
|
587
|
+
require 'chef/provisioning/convergence_strategy/install_msi'
|
588
|
+
require 'chef/provisioning/convergence_strategy/install_cached'
|
589
|
+
require 'chef/provisioning/convergence_strategy/no_converge'
|
590
|
+
|
591
|
+
mopts = machine_options[:convergence_options].to_hash.dup
|
592
|
+
if mopts[:chef_server]
|
593
|
+
mopts[:chef_server] = mopts[:chef_server].to_hash.dup
|
594
|
+
mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
|
595
|
+
end
|
596
|
+
|
597
|
+
if !machine_spec.location
|
598
|
+
return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
|
599
|
+
mopts, config)
|
600
|
+
end
|
601
|
+
|
602
|
+
if machine_spec.location['is_windows']
|
603
|
+
Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
|
604
|
+
mopts, config)
|
605
|
+
else
|
606
|
+
Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
|
607
|
+
mopts, config)
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def wait_for_transport(action_handler, machine_spec, machine_options, vm)
|
612
|
+
transport = transport_for(
|
613
|
+
machine_spec,
|
614
|
+
machine_options[:bootstrap_options][:ssh]
|
615
|
+
)
|
616
|
+
if !transport.available?
|
617
|
+
if action_handler.should_perform_actions
|
618
|
+
action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
|
619
|
+
|
620
|
+
until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
|
621
|
+
print "."
|
622
|
+
sleep 5
|
623
|
+
end
|
624
|
+
|
625
|
+
action_handler.report_progress "#{machine_spec.name} is now connectable"
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|
629
|
+
|
630
|
+
def transport_for(
|
631
|
+
machine_spec,
|
632
|
+
remoting_options,
|
633
|
+
ip = machine_spec.location['ipaddress']
|
634
|
+
)
|
635
|
+
if machine_spec.location['is_windows']
|
636
|
+
create_winrm_transport(ip, remoting_options)
|
637
|
+
else
|
638
|
+
create_ssh_transport(ip, remoting_options)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def create_winrm_transport(host, options)
|
643
|
+
require 'chef/provisioning/transport/winrm'
|
644
|
+
opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
|
645
|
+
winrm_options = {
|
646
|
+
user: "#{options[:user]}",
|
647
|
+
pass: options[:password],
|
648
|
+
opt => true
|
649
|
+
}
|
650
|
+
|
651
|
+
Chef::Provisioning::Transport::WinRM.new(
|
652
|
+
"http://#{host}:5985/wsman",
|
653
|
+
:plaintext,
|
654
|
+
winrm_options,
|
655
|
+
config
|
656
|
+
)
|
657
|
+
end
|
658
|
+
|
659
|
+
def create_ssh_transport(host, options)
|
660
|
+
require 'chef/provisioning/transport/ssh'
|
661
|
+
ssh_user = options[:user]
|
662
|
+
Chef::Provisioning::Transport::SSH.new(
|
663
|
+
host,
|
664
|
+
ssh_user,
|
665
|
+
options,
|
666
|
+
@config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
|
667
|
+
config
|
668
|
+
)
|
669
|
+
end
|
670
|
+
|
671
|
+
def ip_to_bootstrap(bootstrap_options, vm)
|
672
|
+
if has_static_ip(bootstrap_options)
|
673
|
+
bootstrap_options[:customization_spec][:ipsettings][:ip]
|
674
|
+
else
|
675
|
+
vm.guest.ipAddress
|
676
|
+
end
|
677
|
+
end
|
678
|
+
end
|
679
|
+
end
|