chef-provisioning-vsphere 0.8.3.dev.2 → 0.8.3

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