chef-provisioning-vsphere 0.8.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,679 +1,688 @@
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.to_hash,
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
+ winrm_transport =
645
+ options[:winrm_transport].nil? ? :negotiate : options[:winrm_transport].to_sym
646
+ port = options[:port] || winrm_transport == :ssl ? '5986' : '5985'
647
+ winrm_options = {
648
+ user: "#{options[:user]}",
649
+ pass: options[:password]
650
+ }
651
+ if options[:winrm_opts].nil?
652
+ opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
653
+ winrm_options.merge!(opt => true)
654
+ else
655
+ winrm_options.merge!(options[:winrm_opts])
656
+ end
657
+ endpoint = "http#{winrm_transport == :ssl ? 's' : ''}://"\
658
+ "#{host}:#{port}/wsman"
659
+
660
+ Chef::Provisioning::Transport::WinRM.new(
661
+ endpoint,
662
+ winrm_transport,
663
+ winrm_options,
664
+ config
665
+ )
666
+ end
667
+
668
+ def create_ssh_transport(host, options)
669
+ require 'chef/provisioning/transport/ssh'
670
+ ssh_user = options[:user]
671
+ Chef::Provisioning::Transport::SSH.new(
672
+ host,
673
+ ssh_user,
674
+ options.to_hash,
675
+ @config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
676
+ config
677
+ )
678
+ end
679
+
680
+ def ip_to_bootstrap(bootstrap_options, vm)
681
+ if has_static_ip(bootstrap_options)
682
+ bootstrap_options[:customization_spec][:ipsettings][:ip]
683
+ else
684
+ vm.guest.ipAddress
685
+ end
686
+ end
687
+ end
688
+ end