chef-provisioning-vsphere 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,671 +1,671 @@
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, 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, 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, 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
- until (Time.now.utc - start) > ready_timeout || has_ip?(vm_ip, vm) do
355
- action_handler.report_progress(
356
- "IP addresses found: #{all_ips_for(vm)}")
357
- vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
358
- sleep 5
359
- end
360
- end
361
-
362
- def all_ips_for(vm)
363
- vm.guest.net.map { |net| net.ipAddress}.flatten
364
- end
365
-
366
- def has_ip?(ip, vm)
367
- all_ips_for(vm).include?(ip)
368
- end
369
-
370
- # Connect to machine without acquiring it
371
- def connect_to_machine(machine_spec, machine_options)
372
- merge_options! machine_options
373
- machine_for(machine_spec, machine_options)
374
- end
375
-
376
- def destroy_machine(action_handler, machine_spec, machine_options)
377
- merge_options! machine_options
378
- vm = vm_for(machine_spec)
379
- if vm
380
- action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
381
- begin
382
- vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
383
- vm.Destroy_Task.wait_for_completion
384
- rescue RbVmomi::Fault => fault
385
- raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
386
- ensure
387
- machine_spec.location = nil
388
- end
389
- end
390
- end
391
- strategy = convergence_strategy_for(machine_spec, machine_options)
392
- strategy.cleanup_convergence(action_handler, machine_spec)
393
- end
394
-
395
- def stop_machine(action_handler, machine_spec, machine_options)
396
- merge_options! machine_options
397
- vm = vm_for(machine_spec)
398
- if vm
399
- action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
400
- vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
401
- end
402
- end
403
- end
404
-
405
- def start_machine(action_handler, machine_spec, machine_options)
406
- merge_options! machine_options
407
- vm = vm_for(machine_spec)
408
- if vm
409
- action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
410
- vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
411
- end
412
- end
413
- vm
414
- end
415
-
416
- def restart_server(action_handler, machine_spec, machine_options)
417
- action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
418
- stop_machine(action_handler, machine_spec, machine_options)
419
- start_machine(action_handler, machine_spec, machine_options)
420
- machine_spec.location['started_at'] = Time.now.utc.to_s
421
- end
422
- end
423
-
424
- protected
425
-
426
- def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
427
- host_lookup = machine.execute_always('host google.com')
428
- if host_lookup.exitstatus != 0
429
- if host_lookup.stdout.include?("setlocale: LC_ALL")
430
- machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
431
- end
432
- distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
433
- Chef::Log.info "Found distro:#{distro}"
434
- if distro == 'Ubuntu'
435
- distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
436
- Chef::Log.info "Found distro version:#{distro_version}"
437
- if distro_version>= 12.04
438
- Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
439
- interfaces_file = "/etc/network/interfaces"
440
- nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
441
- machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
442
- machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
443
- machine.execute_always('/etc/init.d/networking restart')
444
- machine.execute_always('apt-get -qq update')
445
- end
446
- end
447
- end
448
- end
449
-
450
- def has_static_ip(bootstrap_options)
451
- if bootstrap_options.has_key?(:customization_spec)
452
- bootstrap_options = bootstrap_options[:customization_spec]
453
- if bootstrap_options.has_key?(:ipsettings)
454
- bootstrap_options = bootstrap_options[:ipsettings]
455
- if bootstrap_options.has_key?(:ip)
456
- return true
457
- end
458
- end
459
- end
460
- false
461
- end
462
-
463
- def remaining_wait_time(machine_spec, machine_options)
464
- if machine_spec.location['started_at']
465
- (machine_options[:start_timeout] || 600) -
466
- (Time.now.utc - Time.parse(machine_spec.location['started_at']))
467
- else
468
- (machine_options[:create_timeout] || 600) -
469
- (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
470
- end
471
- end
472
-
473
- def wait_until_ready(action_handler, machine_spec, machine_options, vm)
474
- if vm.guest.toolsRunningStatus != "guestToolsRunning"
475
- if action_handler.should_perform_actions
476
- action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
477
- until remaining_wait_time(machine_spec, machine_options) < 0 ||
478
- (vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
479
- print "."
480
- sleep 5
481
- end
482
- action_handler.report_progress "#{machine_spec.name} is now ready"
483
- end
484
- end
485
- end
486
-
487
- def vm_for(machine_spec)
488
- if machine_spec.location
489
- vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
490
- else
491
- nil
492
- end
493
- end
494
-
495
- def clone_vm(action_handler, bootstrap_options, machine_name)
496
- vm_template = vm_template_for(bootstrap_options)
497
-
498
- spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
499
- clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
500
- Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
501
-
502
- vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
503
- vm_template.CloneVM_Task(
504
- name: machine_name,
505
- folder: vm_folder,
506
- spec: clone_spec
507
- ).wait_for_completion
508
-
509
- vm = vsphere_helper.find_vm(vm_folder, machine_name)
510
-
511
- additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
512
- if !additional_disk_size_gb.is_a?(Array)
513
- additional_disk_size_gb = [additional_disk_size_gb]
514
- end
515
-
516
- additional_disk_size_gb.each do |size|
517
- size = size.to_i
518
- next if size == 0
519
- if bootstrap_options[:datastore].to_s.empty?
520
- raise ':datastore must be specified when adding a disk to a cloned vm'
521
- end
522
- task = vm.ReconfigVM_Task(
523
- spec: RbVmomi::VIM.VirtualMachineConfigSpec(
524
- deviceChange: [
525
- vsphere_helper.virtual_disk_for(
526
- vm,
527
- bootstrap_options[:datastore],
528
- size
529
- )
530
- ]
531
- )
532
- )
533
- task.wait_for_completion
534
- end
535
-
536
- vm
537
- end
538
-
539
- def vsphere_helper
540
- @vsphere_helper ||= VsphereHelper.new(
541
- connect_options,
542
- config[:machine_options][:bootstrap_options][:datacenter]
543
- )
544
- end
545
-
546
- def vm_template_for(bootstrap_options)
547
- template_folder = bootstrap_options[:template_folder]
548
- template_name = bootstrap_options[:template_name]
549
- vsphere_helper.find_vm(template_folder, template_name) ||
550
- raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
551
- end
552
-
553
- def machine_for(machine_spec, machine_options)
554
- if machine_spec.location.nil?
555
- raise "Server for node #{machine_spec.name} has not been created!"
556
- end
557
-
558
- transport = transport_for(
559
- machine_spec,
560
- machine_options[:bootstrap_options][:ssh]
561
- )
562
- strategy = convergence_strategy_for(machine_spec, machine_options)
563
-
564
- if machine_spec.location['is_windows']
565
- Chef::Provisioning::Machine::WindowsMachine.new(
566
- machine_spec, transport, strategy)
567
- else
568
- Chef::Provisioning::Machine::UnixMachine.new(
569
- machine_spec, transport, strategy)
570
- end
571
- end
572
-
573
- def is_windows?(vm)
574
- return false if vm.nil?
575
- vm.config.guestId.start_with?('win')
576
- end
577
-
578
- def convergence_strategy_for(machine_spec, machine_options)
579
- require 'chef/provisioning/convergence_strategy/install_msi'
580
- require 'chef/provisioning/convergence_strategy/install_cached'
581
- require 'chef/provisioning/convergence_strategy/no_converge'
582
-
583
- mopts = machine_options[:convergence_options].to_hash.dup
584
- if mopts[:chef_server]
585
- mopts[:chef_server] = mopts[:chef_server].to_hash.dup
586
- mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
587
- end
588
-
589
- if !machine_spec.location
590
- return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
591
- mopts, config)
592
- end
593
-
594
- if machine_spec.location['is_windows']
595
- Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
596
- mopts, config)
597
- else
598
- Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
599
- mopts, config)
600
- end
601
- end
602
-
603
- def wait_for_transport(action_handler, machine_spec, machine_options, vm)
604
- transport = transport_for(
605
- machine_spec,
606
- machine_options[:bootstrap_options][:ssh]
607
- )
608
- if !transport.available?
609
- if action_handler.should_perform_actions
610
- action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
611
-
612
- until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
613
- print "."
614
- sleep 5
615
- end
616
-
617
- action_handler.report_progress "#{machine_spec.name} is now connectable"
618
- end
619
- end
620
- end
621
-
622
- def transport_for(
623
- machine_spec,
624
- remoting_options,
625
- ip = machine_spec.location['ipaddress']
626
- )
627
- if machine_spec.location['is_windows']
628
- create_winrm_transport(ip, remoting_options)
629
- else
630
- create_ssh_transport(ip, remoting_options)
631
- end
632
- end
633
-
634
- def create_winrm_transport(host, options)
635
- require 'chef/provisioning/transport/winrm'
636
- opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
637
- winrm_options = {
638
- user: "#{options[:user]}",
639
- pass: options[:password],
640
- opt => true
641
- }
642
-
643
- Chef::Provisioning::Transport::WinRM.new(
644
- "http://#{host}:5985/wsman",
645
- :plaintext,
646
- winrm_options,
647
- config
648
- )
649
- end
650
-
651
- def create_ssh_transport(host, options)
652
- require 'chef/provisioning/transport/ssh'
653
- ssh_user = options[:user]
654
- Chef::Provisioning::Transport::SSH.new(
655
- host,
656
- ssh_user,
657
- options,
658
- @config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
659
- config
660
- )
661
- end
662
-
663
- def ip_to_bootstrap(bootstrap_options, vm)
664
- if has_static_ip(bootstrap_options)
665
- bootstrap_options[:customization_spec][:ipsettings][:ip]
666
- else
667
- vm.guest.ipAddress
668
- end
669
- end
670
- end
671
- 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, 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, 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, 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
+ until (Time.now.utc - start) > ready_timeout || has_ip?(vm_ip, vm) do
355
+ action_handler.report_progress(
356
+ "IP addresses found: #{all_ips_for(vm)}")
357
+ vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
358
+ sleep 5
359
+ end
360
+ end
361
+
362
+ def all_ips_for(vm)
363
+ vm.guest.net.map { |net| net.ipAddress}.flatten
364
+ end
365
+
366
+ def has_ip?(ip, vm)
367
+ all_ips_for(vm).include?(ip)
368
+ end
369
+
370
+ # Connect to machine without acquiring it
371
+ def connect_to_machine(machine_spec, machine_options)
372
+ merge_options! machine_options
373
+ machine_for(machine_spec, machine_options)
374
+ end
375
+
376
+ def destroy_machine(action_handler, machine_spec, machine_options)
377
+ merge_options! machine_options
378
+ vm = vm_for(machine_spec)
379
+ if vm
380
+ action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
381
+ begin
382
+ vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
383
+ vm.Destroy_Task.wait_for_completion
384
+ rescue RbVmomi::Fault => fault
385
+ raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
386
+ ensure
387
+ machine_spec.location = nil
388
+ end
389
+ end
390
+ end
391
+ strategy = convergence_strategy_for(machine_spec, machine_options)
392
+ strategy.cleanup_convergence(action_handler, machine_spec)
393
+ end
394
+
395
+ def stop_machine(action_handler, machine_spec, machine_options)
396
+ merge_options! machine_options
397
+ vm = vm_for(machine_spec)
398
+ if vm
399
+ action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
400
+ vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
401
+ end
402
+ end
403
+ end
404
+
405
+ def start_machine(action_handler, machine_spec, machine_options)
406
+ merge_options! machine_options
407
+ vm = vm_for(machine_spec)
408
+ if vm
409
+ action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
410
+ vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
411
+ end
412
+ end
413
+ vm
414
+ end
415
+
416
+ def restart_server(action_handler, machine_spec, machine_options)
417
+ action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
418
+ stop_machine(action_handler, machine_spec, machine_options)
419
+ start_machine(action_handler, machine_spec, machine_options)
420
+ machine_spec.location['started_at'] = Time.now.utc.to_s
421
+ end
422
+ end
423
+
424
+ protected
425
+
426
+ def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
427
+ host_lookup = machine.execute_always('host google.com')
428
+ if host_lookup.exitstatus != 0
429
+ if host_lookup.stdout.include?("setlocale: LC_ALL")
430
+ machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
431
+ end
432
+ distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
433
+ Chef::Log.info "Found distro:#{distro}"
434
+ if distro == 'Ubuntu'
435
+ distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
436
+ Chef::Log.info "Found distro version:#{distro_version}"
437
+ if distro_version>= 12.04
438
+ Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
439
+ interfaces_file = "/etc/network/interfaces"
440
+ nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
441
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
442
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
443
+ machine.execute_always('/etc/init.d/networking restart')
444
+ machine.execute_always('apt-get -qq update')
445
+ end
446
+ end
447
+ end
448
+ end
449
+
450
+ def has_static_ip(bootstrap_options)
451
+ if bootstrap_options.has_key?(:customization_spec)
452
+ bootstrap_options = bootstrap_options[:customization_spec]
453
+ if bootstrap_options.has_key?(:ipsettings)
454
+ bootstrap_options = bootstrap_options[:ipsettings]
455
+ if bootstrap_options.has_key?(:ip)
456
+ return true
457
+ end
458
+ end
459
+ end
460
+ false
461
+ end
462
+
463
+ def remaining_wait_time(machine_spec, machine_options)
464
+ if machine_spec.location['started_at']
465
+ (machine_options[:start_timeout] || 600) -
466
+ (Time.now.utc - Time.parse(machine_spec.location['started_at']))
467
+ else
468
+ (machine_options[:create_timeout] || 600) -
469
+ (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
470
+ end
471
+ end
472
+
473
+ def wait_until_ready(action_handler, machine_spec, machine_options, vm)
474
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
475
+ if action_handler.should_perform_actions
476
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
477
+ until remaining_wait_time(machine_spec, machine_options) < 0 ||
478
+ (vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
479
+ print "."
480
+ sleep 5
481
+ end
482
+ action_handler.report_progress "#{machine_spec.name} is now ready"
483
+ end
484
+ end
485
+ end
486
+
487
+ def vm_for(machine_spec)
488
+ if machine_spec.location
489
+ vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
490
+ else
491
+ nil
492
+ end
493
+ end
494
+
495
+ def clone_vm(action_handler, bootstrap_options, machine_name)
496
+ vm_template = vm_template_for(bootstrap_options)
497
+
498
+ spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
499
+ clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
500
+ Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
501
+
502
+ vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
503
+ vm_template.CloneVM_Task(
504
+ name: machine_name,
505
+ folder: vm_folder,
506
+ spec: clone_spec
507
+ ).wait_for_completion
508
+
509
+ vm = vsphere_helper.find_vm(vm_folder, machine_name)
510
+
511
+ additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
512
+ if !additional_disk_size_gb.is_a?(Array)
513
+ additional_disk_size_gb = [additional_disk_size_gb]
514
+ end
515
+
516
+ additional_disk_size_gb.each do |size|
517
+ size = size.to_i
518
+ next if size == 0
519
+ if bootstrap_options[:datastore].to_s.empty?
520
+ raise ':datastore must be specified when adding a disk to a cloned vm'
521
+ end
522
+ task = vm.ReconfigVM_Task(
523
+ spec: RbVmomi::VIM.VirtualMachineConfigSpec(
524
+ deviceChange: [
525
+ vsphere_helper.virtual_disk_for(
526
+ vm,
527
+ bootstrap_options[:datastore],
528
+ size
529
+ )
530
+ ]
531
+ )
532
+ )
533
+ task.wait_for_completion
534
+ end
535
+
536
+ vm
537
+ end
538
+
539
+ def vsphere_helper
540
+ @vsphere_helper ||= VsphereHelper.new(
541
+ connect_options,
542
+ config[:machine_options][:bootstrap_options][:datacenter]
543
+ )
544
+ end
545
+
546
+ def vm_template_for(bootstrap_options)
547
+ template_folder = bootstrap_options[:template_folder]
548
+ template_name = bootstrap_options[:template_name]
549
+ vsphere_helper.find_vm(template_folder, template_name) ||
550
+ raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
551
+ end
552
+
553
+ def machine_for(machine_spec, machine_options)
554
+ if machine_spec.location.nil?
555
+ raise "Server for node #{machine_spec.name} has not been created!"
556
+ end
557
+
558
+ transport = transport_for(
559
+ machine_spec,
560
+ machine_options[:bootstrap_options][:ssh]
561
+ )
562
+ strategy = convergence_strategy_for(machine_spec, machine_options)
563
+
564
+ if machine_spec.location['is_windows']
565
+ Chef::Provisioning::Machine::WindowsMachine.new(
566
+ machine_spec, transport, strategy)
567
+ else
568
+ Chef::Provisioning::Machine::UnixMachine.new(
569
+ machine_spec, transport, strategy)
570
+ end
571
+ end
572
+
573
+ def is_windows?(vm)
574
+ return false if vm.nil?
575
+ vm.config.guestId.start_with?('win')
576
+ end
577
+
578
+ def convergence_strategy_for(machine_spec, machine_options)
579
+ require 'chef/provisioning/convergence_strategy/install_msi'
580
+ require 'chef/provisioning/convergence_strategy/install_cached'
581
+ require 'chef/provisioning/convergence_strategy/no_converge'
582
+
583
+ mopts = machine_options[:convergence_options].to_hash.dup
584
+ if mopts[:chef_server]
585
+ mopts[:chef_server] = mopts[:chef_server].to_hash.dup
586
+ mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
587
+ end
588
+
589
+ if !machine_spec.location
590
+ return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
591
+ mopts, config)
592
+ end
593
+
594
+ if machine_spec.location['is_windows']
595
+ Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
596
+ mopts, config)
597
+ else
598
+ Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
599
+ mopts, config)
600
+ end
601
+ end
602
+
603
+ def wait_for_transport(action_handler, machine_spec, machine_options, vm)
604
+ transport = transport_for(
605
+ machine_spec,
606
+ machine_options[:bootstrap_options][:ssh]
607
+ )
608
+ if !transport.available?
609
+ if action_handler.should_perform_actions
610
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
611
+
612
+ until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
613
+ print "."
614
+ sleep 5
615
+ end
616
+
617
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
618
+ end
619
+ end
620
+ end
621
+
622
+ def transport_for(
623
+ machine_spec,
624
+ remoting_options,
625
+ ip = machine_spec.location['ipaddress']
626
+ )
627
+ if machine_spec.location['is_windows']
628
+ create_winrm_transport(ip, remoting_options)
629
+ else
630
+ create_ssh_transport(ip, remoting_options)
631
+ end
632
+ end
633
+
634
+ def create_winrm_transport(host, options)
635
+ require 'chef/provisioning/transport/winrm'
636
+ opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
637
+ winrm_options = {
638
+ user: "#{options[:user]}",
639
+ pass: options[:password],
640
+ opt => true
641
+ }
642
+
643
+ Chef::Provisioning::Transport::WinRM.new(
644
+ "http://#{host}:5985/wsman",
645
+ :plaintext,
646
+ winrm_options,
647
+ config
648
+ )
649
+ end
650
+
651
+ def create_ssh_transport(host, options)
652
+ require 'chef/provisioning/transport/ssh'
653
+ ssh_user = options[:user]
654
+ Chef::Provisioning::Transport::SSH.new(
655
+ host,
656
+ ssh_user,
657
+ options,
658
+ @config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
659
+ config
660
+ )
661
+ end
662
+
663
+ def ip_to_bootstrap(bootstrap_options, vm)
664
+ if has_static_ip(bootstrap_options)
665
+ bootstrap_options[:customization_spec][:ipsettings][:ip]
666
+ else
667
+ vm.guest.ipAddress
668
+ end
669
+ end
670
+ end
671
+ end