Class: ChefProvisioningVsphere::VsphereDriver

Inherits:
Chef::Provisioning::Driver
  • Object
show all
Includes:
Chef::Mixin::ShellOut
Defined in:
lib/chef/provisioning/vsphere_driver/driver.rb

Overview

Inherits the Chef::Provisioning::Driver attirbutes

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(driver_url, config) ⇒ VsphereDriver

Initialize method for this class

Parameters:

  • driver_url (String)

    Location of where the driver needs to connect to.

  • config (Object)

    The complete config of the machine options.



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 73

def initialize(driver_url, config)
  super(driver_url, config)

  uri = URI(driver_url)
  @connect_options = {
    provider: "vsphere",
    host: uri.host,
    port: uri.port,
    use_ssl: uri.use_ssl,
    insecure: uri.insecure,
    path: uri.path,
  }

  if driver_options
    @connect_options[:user] = driver_options[:user]
    @connect_options[:password] = driver_options[:password]
  end
end

Instance Attribute Details

#connect_optionsObject (readonly)

Returns the value of attribute connect_options.



92
93
94
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 92

def connect_options
  @connect_options
end

Class Method Details

.canonicalize_url(driver_url, config) ⇒ Object

Create a new Vsphere provisioner. ## Parameters connect_options - hash of options to be passed to RbVmomi::VIM.connect

:host       - required - hostname of the vSphere API server
:port       - optional - port on the vSphere API server (default: 443)
:path        - optional - path on the vSphere API server (default: /sdk)
:use_ssl        - optional - true to use ssl in connection to vSphere API server (default: true)
:insecure   - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
:user       - required - user name to use in connection to vSphere API server
:password   - required - password to use in connection to vSphere API server


38
39
40
41
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 38

def self.canonicalize_url(driver_url, config)
  config = symbolize_keys(config)
  [driver_url || URI::VsphereUrl.from_config(config).to_s, config]
end

.from_url(driver_url, config) ⇒ Object

Creates the new object via the URL

Parameters:

  • driver_url (String)

    The driver url to connect via.

  • config (Object)

    The config to get to the machine you are creating.



24
25
26
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 24

def self.from_url(driver_url, config)
  VsphereDriver.new(driver_url, config)
end

.symbolize_keys(h) ⇒ Object

Converts the keys from strings to symbols

Parameters:

  • h (Object)

    converts to key:value



46
47
48
49
50
51
52
53
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 46

def self.symbolize_keys(h)
  Hash === h ? # rubocop:disable Style/MultilineTernaryOperator
    Hash[
      h.map do |k, v|
        [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)] # rubocop:disable Style/NestedTernaryOperator
      end
    ] : h
end

Instance Method Details

#add_machine_spec_location(vm, machine_spec) ⇒ Object

Adds machine spec location from other options.

Parameters:

  • machine_spec (Object)

    taken from Chef provisioning for all the ‘machine_spec`.

  • vm (Object)

    taken from Chef provisioning for all the vm state.



197
198
199
200
201
202
203
204
205
206
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 197

def add_machine_spec_location(vm, machine_spec)
  machine_spec.location = {
    "driver_url" => driver_url,
    "driver_version" => VERSION,
    "server_id" => vm.config.instanceUuid,
    "is_windows" => is_windows?(vm),
    "allocated_at" => Time.now.utc.to_s,
    "ipaddress" => vm.guest.ipAddress,
  }
end

#all_ips_for(vm) ⇒ Object

Flatten all IPs for a VM

Parameters:

  • vm (Object)

    The VM object.



494
495
496
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 494

def all_ips_for(vm)
  vm.guest.net.map(&:ipAddress).flatten
end

#allocate_machine(action_handler, machine_spec, machine_options) ⇒ Object

Acquire a machine, generally by provisioning it. Returns a Machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory. The Machine object will have a “node” property which must be saved to the server (if it is any different from the original node object).

## Parameters action_handler - the action_handler object that is calling this method; this

is generally a action_handler, but could be anything that can support the
ChefMetal::ActionHandler interface (i.e., in the case of the test
kitchen metal driver for acquiring and destroying VMs; see the base
class for what needs providing).

node - node object (deserialized json) representing this machine. If

the node has a provisioner_options hash in it, these will be used
instead of options provided by the provisioner.  TODO compare and
fail if different?
node will have node['normal']['provisioner_options'] in it with any options.
It is a hash with this format:
   -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
   -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
        :datacenter
        :resource_pool
        :cluster
        :datastore
        :template_name
        :template_folder
        :vm_folder
        :winrm {...} (not yet implemented)
        :ssh {...}

Example bootstrap_options for vSphere:
  TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
  'bootstrap_options' => {
    'template_name' =>'centos6.small',
    'template_folder' =>'Templates',
    'vm_folder' => 'MyApp'
  }

node['normal']['provisioner_output'] will be populated with information
about the created machine.  For vSphere, it is a hash with this
format:
   -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
   -- vm_folder: name of the vSphere folder containing the VM


144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 144

def allocate_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options

  if machine_spec.location
    Chef::Log.warn(
      "Checking to see if #{machine_spec.location} has been created..."
    )
    vm = vm_for(machine_spec)
    if vm
      Chef::Log.warn "returning existing machine"
      return vm
    else
      Chef::Log.warn machine_msg(
        machine_spec.name,
        machine_spec.location["server_id"],
        "no longer exists.  Recreating ..."
      )
    end
  end
  bootstrap_options = machine_options[:bootstrap_options]

  action_handler.report_progress full_description(
    machine_spec, bootstrap_options
  )

  vm = find_or_create_vm(bootstrap_options, machine_spec, action_handler)

  add_machine_spec_location(vm, machine_spec)

  action_handler.performed_action(machine_msg(
                                    machine_spec.name,
                                    vm.config.instanceUuid,
                                    "created"
  ))
  vm
end

#attempt_ip(machine_options, action_handler, vm, machine_spec) ⇒ Object

Test out the IP to connect to.

Parameters:

  • action_handler (Object)

    TODO

  • machine_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • vm (Object)

    The VM object.

  • machine_spec (Object)

    The spec required to talk to the VM.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 394

def attempt_ip(machine_options, action_handler, vm, machine_spec)
  vm_ip = ip_to_bootstrap(machine_options[:bootstrap_options], vm)
  machine_spec.location["ipaddress"] = vm_ip

  wait_for_ip(vm, machine_options, machine_spec, action_handler)

  unless has_ip?(vm_ip, vm)
    action_handler.report_progress "rebooting..."
    if vm.guest.toolsRunningStatus != "guestToolsRunning"
      msg = "tools have stopped. current power state is "
      msg << vm.runtime.powerState
      msg << " and tools state is "
      msg << vm.guest.toolsRunningStatus
      msg << ". powering up server..."
      action_handler.report_progress(msg)
      vsphere_helper.start_vm(vm)
    else
      restart_server(action_handler, machine_spec, machine_options)
    end
    wait_for_ip(vm, machine_options, machine_spec, action_handler)
  end
end

#connect_to_machine(machine_spec, machine_options) ⇒ Object

Connect to machine without acquiring it

Parameters:

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.



510
511
512
513
514
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 510

def connect_to_machine(machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options
  machine_for(machine_spec, machine_options)
end

#deep_symbolize(hash_like) ⇒ Object

Converts the keys from strings to symbols

Parameters:

  • hash_like (Object)

    converts to key:value



58
59
60
61
62
63
64
65
66
67
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 58

def deep_symbolize(hash_like)
  return {} if hash_like.nil?

  r = {}
  hash_like.each do |key, value|
    value = deep_symbolize(value) if value.respond_to?(:values)
    r[key.to_sym] = value
  end
  r
end

#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object

Destroy the Machine

Parameters:

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.

  • action_handler (Object)

    TODO



521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 521

def destroy_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options
  vm = vm_for(machine_spec)
  if vm
    action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
      begin
        vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
        vm.Destroy_Task.wait_for_completion
      rescue RbVmomi::Fault => fault
        raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
      ensure
        machine_spec.location = nil
      end
    end
  end
  strategy = convergence_strategy_for(machine_spec, machine_options)
  strategy.cleanup_convergence(action_handler, machine_spec)
end

#find_or_create_vm(bootstrap_options, machine_spec, action_handler) ⇒ Object

Verify if machine is there or create it.

Parameters:

  • bootstrap_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • machine_spec (Object)

    taken from Chef provisioning for all the ‘machine_spec`.

  • action_handler (Object)

    taken from Chef provisioning for TODO.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 213

def find_or_create_vm(bootstrap_options, machine_spec, action_handler)
  vm = vsphere_helper.find_vm(
    bootstrap_options[:vm_folder],
    machine_spec.name
  )
  if vm
    Chef::Log.info machine_msg(
      machine_spec.name,
      vm.config.instanceUuid,
      "already created"
    )
  else
    vm = clone_vm(
      action_handler,
      bootstrap_options,
      machine_spec
    )
  end
  vm
end

#full_description(machine_spec, bootstrap_options) ⇒ Object

Creates a full description of the machine.

Parameters:

  • bootstrap_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • machine_spec (Object)

    taken from Chef provisioning for all the ‘machine_spec`.



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 238

def full_description(machine_spec, bootstrap_options)
  description = ["creating machine #{machine_spec.name} on #{driver_url}"]
  bootstrap_options.to_hash.each_pair do |key, value|
    if value.is_a?(Hash)
      temp_value = value.clone
      temp_value[:password] = "*********" if value.key?(:password)
    else
      temp_value = value
    end
    description << "  #{key}: #{temp_value.inspect}"
  end
  description
end

#has_ip?(ip, vm) ⇒ Boolean

Does the VM have this IP?

Parameters:

  • vm (Object)

    The VM object.

  • ip (String)

    IP to see if it’s in the map.

Returns:

  • (Boolean)


502
503
504
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 502

def has_ip?(ip, vm)
  all_ips_for(vm).include?(ip)
end

#machine_msg(name, id, action) ⇒ String

Creates a string of specific Machine information

Parameters:

  • name (String)

    The name of the machine

  • id (String)

    The ID of the machine

  • action (Object)

    TODO

Returns:

  • (String)

    “Machine - ACTION - NAME (UUID on URL)”



258
259
260
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 258

def machine_msg(name, id, action)
  "Machine - #{action} - #{name} (#{id} on #{driver_url})"
end

#merge_options!(machine_options) ⇒ Object

Squishes the options to one large hash

Parameters:

  • machine_options (Object)

    taken from Chef provisioning for all the ‘machine_options`



185
186
187
188
189
190
191
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 185

def merge_options!(machine_options)
  @config = Cheffish::MergedConfig.new(
    { machine_options: machine_options },
    @config
  )
  @config = deep_symbolize(@config.to_h)
end

#ready_machine(action_handler, machine_spec, machine_options) ⇒ Object

Sets the machine to ready state. (Creates the machine but does not bootstrap Chef into it.)

Parameters:

  • action_handler (Object)

    TODO

  • machine_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • machine_spec (Object)

    taken from Chef provisioning for all the ‘machine_spec`.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 267

def ready_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options

  vm = start_machine(action_handler, machine_spec, machine_options)
  if vm.nil?
    raise "Machine #{machine_spec.name} does not have a server "\
    "associated with it, or server does not exist."
  end

  bootstrap_options = machine_options[:bootstrap_options]

  transport_respond?(
    machine_options,
    vm,
    action_handler,
    machine_spec
  )

  machine = machine_for(machine_spec, machine_options)

  setup_extra_nics(action_handler, bootstrap_options, vm, machine, machine_spec)

  if has_static_ip(bootstrap_options) && !is_windows?(vm)
    setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
  end

  ## Check if true available after added nic
  vm_helper.open_port?(machine_spec.location["ipaddress"], vm_helper.port) unless machine_spec.location["ipaddress"].nil?
  machine
end

#restart_server(action_handler, machine_spec, machine_options) ⇒ Object

Restart the Machine

Parameters:

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.

  • action_handler (Object)

    TODO



579
580
581
582
583
584
585
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 579

def restart_server(action_handler, machine_spec, machine_options)
  action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
    stop_machine(action_handler, machine_spec, machine_options)
    start_machine(action_handler, machine_spec, machine_options)
    machine_spec.location["started_at"] = Time.now.utc.to_s
  end
end

#setup_extra_nics(action_handler, bootstrap_options, vm, machine, machine_spec) ⇒ Object

Creates new NICs for the machine

Parameters:

  • action_handler (Object)

    TODO

  • bootstrap_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • vm (Object)

    The VM object.

  • machine (Object)

    The machine object.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 305

def setup_extra_nics(action_handler, bootstrap_options, vm, machine, machine_spec)
  networks = bootstrap_options[:network_name]
  networks = [networks] if networks.is_a?(String)
  return if networks.nil? || networks.count < 2

  new_nics = vsphere_helper.add_extra_nic(
    action_handler,
    vm_template_for(bootstrap_options),
    bootstrap_options,
    vm
  )
  if is_windows?(vm) && !new_nics.nil? && vm_helper.open_port?(machine_spec.location["ipaddress"], vm_helper.port)
    new_nics.each do |nic|
      nic_label = nic.device.deviceInfo.label
      machine.execute_always(
        "Disable-Netadapter -Name '#{nic_label}' -Confirm:$false"
      )
    end
  end
end

#start_machine(action_handler, machine_spec, machine_options) ⇒ Object

Start the Machine

Parameters:

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.

  • action_handler (Object)

    TODO



562
563
564
565
566
567
568
569
570
571
572
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 562

def start_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options
  vm = vm_for(machine_spec)
  if vm
    action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
      vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
    end
  end
  vm
end

#stop_machine(action_handler, machine_spec, machine_options) ⇒ Object

Stop the Machine

Parameters:

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.

  • action_handler (Object)

    TODO



546
547
548
549
550
551
552
553
554
555
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 546

def stop_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize(machine_options)
  merge_options! machine_options
  vm = vm_for(machine_spec)
  if vm
    action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
      vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
    end
  end
end

#transport_respond?(machine_options, vm, action_handler, machine_spec) ⇒ Boolean

Is the transport layer ready?

Parameters:

  • action_handler (Object)

    TODO

  • machine_spec (Object)

    taken from Chef provisioning for all the bootstrap options.

  • vm (Object)

    The VM object.

  • machine_options (Object)

    The options required to talk to the VM.

Returns:

  • (Boolean)


332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 332

def transport_respond?(
  machine_options,
  vm,
  action_handler,
  machine_spec
)
  bootstrap_options = machine_options[:bootstrap_options]

  # this waits for vmware tools to start and the vm to presebnt an ip
  # This may just be the ip of a newly cloned machine
  # Customization below may change this to a valid ip
  wait_until_ready(action_handler, machine_spec, machine_options, vm)

  if !machine_spec.location["ipaddress"] || !has_ip?(machine_spec.location["ipaddress"], vm)
    # find the ip we actually want
    # this will be the static ip to assign
    # or the ip reported back by the vm if using dhcp
    # it *may* be nil if just cloned
    vm_ip = ip_to_bootstrap(bootstrap_options, vm) || vm.guest.ipAddress
    machine_spec.location["ipaddress"] = vm_ip
    transport = nil
    unless vm_ip.nil?
      transport = transport_for(machine_spec, bootstrap_options[:ssh], vm_ip)
    end

    unless !transport.nil? && transport.available? && has_ip?(vm_ip, vm)
      attempt_ip(machine_options, action_handler, vm, machine_spec)
    end
    machine_spec.location["ipaddress"] = vm_ip # vm.guest.ipAddress vmWare ip_address here can be 0.0.0.0
    action_handler.report_progress(
      "IP address obtained: #{machine_spec.location['ipaddress']}"
    )
  end

  wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)

  begin
    wait_for_transport(action_handler, machine_spec, machine_options, vm)
  rescue Timeout::Error
    # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
    if machine_spec.location["started_at"] ||
        remaining_wait_time(machine_spec, machine_options) < -(10 * 60)
      raise
    else
      Chef::Log.warn(machine_msg(
                       machine_spec.name,
                       vm.config.instanceUuid,
                       "started but SSH did not come up.  Rebooting..."
      ))
      restart_server(action_handler, machine_spec, machine_options)
      wait_until_ready(action_handler, machine_spec, machine_options, vm)
      wait_for_transport(action_handler, machine_spec, machine_options, vm)
    end
  end
end

#vm_helperObject

Creates a new vm_helper if not already there



96
97
98
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 96

def vm_helper
  @vm_helper ||= ChefProvisioningVsphere::VmHelper.new
end

#wait_for_domain(bootstrap_options, vm, machine_spec, action_handler) ⇒ Object

Wait for the Windows Domain.

Parameters:

  • action_handler (Object)

    TODO

  • bootstrap_options (Object)

    taken from Chef provisioning for all the bootstrap options.

  • vm (Object)

    The VM object.

  • machine_spec (Object)

    The specs to connect to bootstrap machine



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 423

def wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
  return unless bootstrap_options[:customization_spec]

  domain = if bootstrap_options[:customization_spec].is_a?(String) && is_windows?(vm)
             spec = vsphere_helper.find_customization_spec(bootstrap_options[:customization_spec])
             spec.identity.identification.joinDomain
           elsif bootstrap_options[:customization_spec].is_a?(String) && !is_windows?(vm)
             spec = vsphere_helper.find_customization_spec(bootstrap_options[:customization_spec])
             spec.identity.domain
           else
             bootstrap_options[:customization_spec][:domain]
           end

  return unless domain

  if is_windows?(vm) && domain != "local"
    start = Time.now.utc
    trimmed_name = machine_spec.name.byteslice(0, 15)
    expected_name = "#{trimmed_name}.#{domain}"
    action_handler.report_progress(
      "waiting to domain join and be named #{expected_name}"
    )
    until (Time.now.utc - start) > 30 ||
        (vm.guest.hostName == expected_name)
      print "."
      sleep 5
    end
  end
end

#wait_for_ip(vm, machine_options, machine_spec, action_handler) ⇒ Object

Wait for another IP allocation.

Parameters:

  • action_handler (Object)

    TODO

  • vm (Object)

    The VM object.

  • machine_options (Object)

    The machine options required to start the VM.

  • machine_spec (Object)

    The machine spec required to start the VM.



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'lib/chef/provisioning/vsphere_driver/driver.rb', line 459

def wait_for_ip(vm, machine_options, machine_spec, action_handler)
  bootstrap_options = machine_options[:bootstrap_options]
  ip_to_bootstrap(bootstrap_options, vm)
  ready_timeout = machine_options[:ready_timeout] || 300
  msg1 = "waiting up to #{ready_timeout} seconds for customization"
  msg2 = " and find #{machine_spec.location['ipaddress']}" unless machine_spec.location["ipaddress"].nil? # unless vm_ip == vm.guest.ipAddress # RuntimeError: can't modify frozen String
  msg = [msg1, msg2].join
  action_handler.report_progress msg

  vm_ip = ip_to_bootstrap(bootstrap_options, vm) || vm.guest.ipAddress
  machine_spec.location["ipaddress"] = vm_ip
  until transport_for(
    machine_spec,
    machine_options[:bootstrap_options][:ssh],
    vm_ip
  ).available? || remaining_wait_time(machine_spec, machine_options) < 0
    action_handler.report_progress(
      "IP addresses found: #{all_ips_for(vm)}"
    )
    vm_ip = ip_to_bootstrap(bootstrap_options, vm) || vm.guest.ipAddress
    machine_spec.location["ipaddress"] = vm_ip
    if has_ip?(vm_ip, vm)
      transport_for(
        machine_spec,
        machine_options[:bootstrap_options][:ssh],
        vm_ip
      ).available?
    end
    sleep 5
  end
end