From c5b8820a9600027d825cece92f4a23d18165e8df Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 21 Oct 2016 13:00:35 +0100 Subject: [PATCH 01/39] Moved helpers into driver and changing actions to use driver. Connection is now no longer exposed to actions. A driver is created per a machine, and it manages the connection itself, so it is now safe between multiple machine deployments. Removed calling @machine.action, so we don't create a lock on the actions anymore, as they are now safe to call in parallel. Moved static strings into locales/en.yml --- lib/vSphere/action.rb | 51 -- lib/vSphere/action/clone.rb | 292 +--------- lib/vSphere/action/connect_vsphere.rb | 29 - lib/vSphere/action/destroy.rb | 13 +- lib/vSphere/action/get_ssh_info.rb | 48 -- lib/vSphere/action/get_state.rb | 41 -- lib/vSphere/action/is_created.rb | 2 +- lib/vSphere/action/is_running.rb | 2 +- lib/vSphere/action/power_off.rb | 16 +- lib/vSphere/action/power_on.rb | 9 +- lib/vSphere/action/snapshot_delete.rb | 21 +- lib/vSphere/action/snapshot_list.rb | 25 - lib/vSphere/action/snapshot_restore.rb | 15 +- lib/vSphere/action/snapshot_save.rb | 22 +- lib/vSphere/action/wait_for_ip_address.rb | 39 +- lib/vSphere/cap/snapshot_list.rb | 3 +- lib/vSphere/driver.rb | 643 ++++++++++++++++++++++ lib/vSphere/provider.rb | 43 +- lib/vSphere/util/vim_helpers.rb | 105 ---- lib/vSphere/util/vm_helpers.rb | 160 ------ 20 files changed, 746 insertions(+), 833 deletions(-) delete mode 100644 lib/vSphere/action/connect_vsphere.rb delete mode 100644 lib/vSphere/action/get_ssh_info.rb delete mode 100644 lib/vSphere/action/get_state.rb delete mode 100644 lib/vSphere/action/snapshot_list.rb create mode 100644 lib/vSphere/driver.rb delete mode 100644 lib/vSphere/util/vim_helpers.rb delete mode 100644 lib/vSphere/util/vm_helpers.rb diff --git a/lib/vSphere/action.rb b/lib/vSphere/action.rb index ad7891a9..da7b266e 100644 --- a/lib/vSphere/action.rb +++ b/lib/vSphere/action.rb @@ -10,7 +10,6 @@ module Action def self.action_destroy Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use ConnectVSphere b.use(ProvisionerCleanup, :before) b.use Call, IsRunning do |env, b2| @@ -97,7 +96,6 @@ def self.action_up Vagrant::Action::Builder.new.tap do |b| b.use HandleBox b.use ConfigValidate - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| if env[:result] b2.use MessageAlreadyCreated @@ -109,7 +107,6 @@ def self.action_up b.use Call, IsRunning do |env, b2| b2.use PowerOn unless env[:result] end - b.use CloseVSphere b.use WaitForIPAddress b.use WaitForCommunicator, [:running] b.use Provision @@ -121,7 +118,6 @@ def self.action_up def self.action_halt Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| unless env[:result] b2.use MessageNotCreated @@ -139,13 +135,11 @@ def self.action_halt end end end - b.use CloseVSphere end end def self.action_reload Vagrant::Action::Builder.new.tap do |b| - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| unless env[:result] b2.use MessageNotCreated @@ -157,25 +151,6 @@ def self.action_reload end end - # vSphere specific actions - def self.action_get_state - Vagrant::Action::Builder.new.tap do |b| - b.use HandleBox - b.use ConfigValidate - b.use ConnectVSphere - b.use GetState - b.use CloseVSphere - end - end - - def self.action_get_ssh_info - Vagrant::Action::Builder.new.tap do |b| - b.use ConfigValidate - b.use ConnectVSphere - b.use GetSshInfo - b.use CloseVSphere - end - end # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. # rubocop:disable IndentationWidth @@ -183,7 +158,6 @@ def self.action_get_ssh_info def self.action_snapshot_delete Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| if env[:result] b2.use SnapshotDelete @@ -191,29 +165,12 @@ def self.action_snapshot_delete b2.use MessageNotCreated end end - b.use CloseVSphere - end - end - - def self.action_snapshot_list - Vagrant::Action::Builder.new.tap do |b| - b.use ConfigValidate - b.use ConnectVSphere - b.use Call, IsCreated do |env, b2| - if env[:result] - b2.use SnapshotList - else - b2.use MessageNotCreated - end - end - b.use CloseVSphere end end def self.action_snapshot_restore Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| unless env[:result] b2.use MessageNotCreated @@ -228,14 +185,12 @@ def self.action_snapshot_restore b2.use action_up end - b.use CloseVSphere end end def self.action_snapshot_save Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use ConnectVSphere b.use Call, IsCreated do |env, b2| if env[:result] b2.use SnapshotSave @@ -243,7 +198,6 @@ def self.action_snapshot_save b2.use MessageNotCreated end end - b.use CloseVSphere end end end # Vagrant > 1.8.0 guard @@ -252,11 +206,7 @@ def self.action_snapshot_save # autoload action_root = Pathname.new(File.expand_path('../action', __FILE__)) autoload :Clone, action_root.join('clone') - autoload :CloseVSphere, action_root.join('close_vsphere') - autoload :ConnectVSphere, action_root.join('connect_vsphere') autoload :Destroy, action_root.join('destroy') - autoload :GetSshInfo, action_root.join('get_ssh_info') - autoload :GetState, action_root.join('get_state') autoload :IsCreated, action_root.join('is_created') autoload :IsRunning, action_root.join('is_running') autoload :MessageAlreadyCreated, action_root.join('message_already_created') @@ -270,7 +220,6 @@ def self.action_snapshot_save # rubocop:disable IndentationWidth if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') autoload :SnapshotDelete, action_root.join('snapshot_delete') - autoload :SnapshotList, action_root.join('snapshot_list') autoload :SnapshotRestore, action_root.join('snapshot_restore') autoload :SnapshotSave, action_root.join('snapshot_save') end diff --git a/lib/vSphere/action/clone.rb b/lib/vSphere/action/clone.rb index 00116cf6..4feddf17 100644 --- a/lib/vSphere/action/clone.rb +++ b/lib/vSphere/action/clone.rb @@ -1,13 +1,10 @@ require 'rbvmomi' require 'i18n' -require 'vSphere/util/vim_helpers' module VagrantPlugins module VSphere module Action class Clone - include Util::VimHelpers - def initialize(app, _env) @app = app end @@ -15,285 +12,28 @@ def initialize(app, _env) def call(env) machine = env[:machine] config = machine.provider_config - connection = env[:vSphere_connection] - name = get_name machine, config, env[:root_path] - dc = get_datacenter connection, machine - template = dc.find_vm config.template_name - fail Errors::VSphereError, :'missing_template' if template.nil? - vm_base_folder = get_vm_base_folder dc, template, config - fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? - - begin - # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf - ds = get_datastore dc, machine - fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) - - location = get_location ds, dc, machine, template - spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false - spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec - customization_info = get_customization_spec_info_by_name connection, machine - - spec[:customization] = get_customization_spec(machine, customization_info) unless customization_info.nil? - - env[:ui].info "Setting custom address: #{config.addressType}" unless config.addressType.nil? - add_custom_address_type(template, spec, config.addressType) unless config.addressType.nil? - - env[:ui].info "Setting custom mac: #{config.mac}" unless config.mac.nil? - add_custom_mac(template, spec, config.mac) unless config.mac.nil? - - env[:ui].info "Setting custom vlan: #{config.vlan}" unless config.vlan.nil? - add_custom_vlan(template, dc, spec, config.vlan) unless config.vlan.nil? - - env[:ui].info "Setting custom memory: #{config.memory_mb}" unless config.memory_mb.nil? - add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? - - env[:ui].info "Setting custom cpu count: #{config.cpu_count}" unless config.cpu_count.nil? - add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? - - env[:ui].info "Setting custom cpu reservation: #{config.cpu_reservation}" unless config.cpu_reservation.nil? - add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? - - env[:ui].info "Setting custom memmory reservation: #{config.mem_reservation}" unless config.mem_reservation.nil? - add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? - add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? - add_custom_notes(spec, config.notes) unless config.notes.nil? - - if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) - - storage_mgr = connection.serviceContent.storageResourceManager - pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) - # TODO: May want to add option on type? - storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) - - env[:ui].info I18n.t('vsphere.requesting_sdrs_recommendation') - env[:ui].info " -- DatastoreCluster: #{ds.name}" - env[:ui].info " -- Template VM: #{template.pretty_path}" - env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}" - - result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) - - recommendation = result.recommendations[0] - key = recommendation.key ||= '' - if key == '' - fail Errors::VSphereError, :missing_datastore_recommendation - end - - env[:ui].info I18n.t('vsphere.creating_cloned_vm_sdrs') - env[:ui].info " -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}" - - apply_sr_result = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]).wait_for_completion - new_vm = apply_sr_result.vm - - else - env[:ui].info I18n.t('vsphere.creating_cloned_vm') - env[:ui].info " -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}" - env[:ui].info " -- Target VM: #{vm_base_folder.pretty_path}/#{name}" - - new_vm = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec).wait_for_completion - - config.custom_attributes.each do |k, v| - env[:ui].info "Setting custom attribute: #{k}=#{v}" - new_vm.setCustomValue(key: k, value: v) - end - end - rescue Errors::VSphereError - raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message + driver = machine.provider.driver + + env[:ui].info "Setting custom address: #{config.addressType}" unless config.addressType.nil? + env[:ui].info "Setting custom mac: #{config.mac}" unless config.mac.nil? + env[:ui].info "Setting custom vlan: #{config.vlan}" unless config.vlan.nil? + env[:ui].info "Setting custom memory: #{config.memory_mb}" unless config.memory_mb.nil? + env[:ui].info "Setting custom cpu count: #{config.cpu_count}" unless config.cpu_count.nil? + env[:ui].info "Setting custom cpu reservation: #{config.cpu_reservation}" unless config.cpu_reservation.nil? + env[:ui].info "Setting custom memmory reservation: #{config.mem_reservation}" unless config.mem_reservation.nil? + + config.custom_attributes.each do |k, v| + env[:ui].info "Setting custom attribute: #{k}=#{v}" end - # TODO: handle interrupted status in the environment, should the vm be destroyed? - - machine.id = new_vm.config.uuid - - wait_for_sysprep(env, new_vm, connection, 600, 10) if config.wait_for_sysprep && machine.config.vm.guest.eql?(:windows) + driver.clone(env[:root_path]) do |progress| + env[:ui].clear_line + env[:ui].report_progress(progress, 100, false) + end env[:ui].info I18n.t('vsphere.vm_clone_success') - @app.call env end - - private - - def wait_for_sysprep(env, vm, vim_connection, timeout, sleep_time) - vem = vim_connection.serviceContent.eventManager - - wait = true - waited_seconds = 0 - - env[:ui].info I18n.t('vsphere.wait_sysprep') - while wait - events = query_customization_succeeded(vm, vem) - - if events.size > 0 - events.each do |e| - env[:ui].info e.fullFormattedMessage - end - wait = false - elsif waited_seconds >= timeout - fail Errors::VSphereError, :'sysprep_timeout' - else - sleep(sleep_time) - waited_seconds += sleep_time - end - end - end - - def query_customization_succeeded(vm, vem) - vem.QueryEvents(filter: - RbVmomi::VIM::EventFilterSpec(entity: - RbVmomi::VIM::EventFilterSpecByEntity(entity: vm, recursion: - RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) - end - - def get_customization_spec(machine, spec_info) - customization_spec = spec_info.spec.clone - - # find all the configured private networks - private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } - return customization_spec if private_networks.nil? - - # make sure we have enough NIC settings to override with the private network settings - fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length - - # assign the private network IP to the NIC - private_networks.each_index do |idx| - customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] - end - - customization_spec - end - - def get_location(datastore, dc, machine, template) - if machine.provider_config.linked_clone - # The API for linked clones is quite strange. We can't create a linked - # straight from any VM. The disks of the VM for which we can create a - # linked clone need to be read-only and thus VC demands that the VM we - # are cloning from uses delta-disks. Only then it will allow us to - # share the base disk. - # - # Thus, this code first create a delta disk on top of the base disk for - # the to-be-cloned VM, if delta disks aren't used already. - disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) - disks.select { |disk| disk.backing.parent.nil? }.each do |disk| - spec = { - deviceChange: [ - { - operation: :remove, - device: disk - }, - { - operation: :add, - fileOperation: :create, - device: disk.dup.tap do |new_disk| - new_disk.backing = new_disk.backing.dup - new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" - new_disk.backing.parent = disk.backing - end - } - ] - } - template.ReconfigVM_Task(spec: spec).wait_for_completion - end - - location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) - elsif datastore.is_a? RbVmomi::VIM::StoragePod - location = RbVmomi::VIM.VirtualMachineRelocateSpec - else - location = RbVmomi::VIM.VirtualMachineRelocateSpec - - location[:datastore] = datastore unless datastore.nil? - end - location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm - location - end - - def get_name(machine, config, root_path) - return config.name unless config.name.nil? - - prefix = "#{root_path.basename}_#{machine.name}" - prefix.gsub!(/[^-a-z0-9_\.]/i, '') - # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs - prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" - end - - def get_vm_base_folder(dc, template, config) - if config.vm_base_path.nil? - template.parent - else - dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) - end - end - - def modify_network_card(template, spec) - spec[:config][:deviceChange] ||= [] - @card ||= template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first - - fail Errors::VSphereError, :missing_network_card if @card.nil? - - yield(@card) - - dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @card, operation: 'edit') - spec[:config][:deviceChange].push dev_spec - spec[:config][:deviceChange].uniq! - end - - def add_custom_address_type(template, spec, addressType) - spec[:config][:deviceChange] = [] - config = template.config - card = config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first || fail(Errors::VSphereError, :missing_network_card) - card.addressType = addressType - card_spec = { :deviceChange => [{ :operation => :edit, :device => card }] } - template.ReconfigVM_Task(:spec => card_spec).wait_for_completion - end - - def add_custom_mac(template, spec, mac) - modify_network_card(template, spec) do |card| - card.macAddress = mac - end - end - - def add_custom_vlan(template, dc, spec, vlan) - network = get_network_by_name(dc, vlan) - - modify_network_card(template, spec) do |card| - begin - switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) - card.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) - rescue - # not connected to a distibuted switch? - card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) - end - end - end - - def add_custom_memory(spec, memory_mb) - spec[:config][:memoryMB] = Integer(memory_mb) - end - - def add_custom_cpu(spec, cpu_count) - spec[:config][:numCPUs] = Integer(cpu_count) - end - - def add_custom_cpu_reservation(spec, cpu_reservation) - spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) - end - - def add_custom_mem_reservation(spec, mem_reservation) - spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) - end - - def add_custom_extra_config(spec, extra_config = {}) - return if extra_config.empty? - - # extraConfig must be an array of hashes with `key` and `value` - # entries. - spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } - end - - def add_custom_notes(spec, notes) - spec[:config][:annotation] = notes - end end end end diff --git a/lib/vSphere/action/connect_vsphere.rb b/lib/vSphere/action/connect_vsphere.rb deleted file mode 100644 index aa8580bb..00000000 --- a/lib/vSphere/action/connect_vsphere.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'rbvmomi' - -module VagrantPlugins - module VSphere - module Action - class ConnectVSphere - def initialize(app, _env) - @app = app - end - - def call(env) - config = env[:machine].provider_config - - begin - env[:vSphere_connection] = RbVmomi::VIM.connect host: config.host, - user: config.user, password: config.password, - insecure: config.insecure, proxyHost: config.proxy_host, - proxyPort: config.proxy_port - @app.call env - rescue Errors::VSphereError - raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message - end - end - end - end - end -end diff --git a/lib/vSphere/action/destroy.rb b/lib/vSphere/action/destroy.rb index d394e616..7c3102e5 100644 --- a/lib/vSphere/action/destroy.rb +++ b/lib/vSphere/action/destroy.rb @@ -1,13 +1,10 @@ require 'rbvmomi' require 'i18n' -require 'vSphere/util/vim_helpers' module VagrantPlugins module VSphere module Action class Destroy - include Util::VimHelpers - def initialize(app, _env) @app = app end @@ -22,13 +19,13 @@ def call(env) private def destroy_vm(env) - return if env[:machine].state.id == :not_created - vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] - return if vm.nil? - begin env[:ui].info I18n.t('vsphere.destroy_vm') - vm.Destroy_Task.wait_for_completion + + env[:machine].provider.driver.destroy do |progress| + env[:ui].clear_line + env[:ui].report_progress(progress, 100, false) + end rescue Errors::VSphereError raise rescue StandardError => e diff --git a/lib/vSphere/action/get_ssh_info.rb b/lib/vSphere/action/get_ssh_info.rb deleted file mode 100644 index dc0a1c2d..00000000 --- a/lib/vSphere/action/get_ssh_info.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'rbvmomi' -require 'vSphere/util/vim_helpers' - -module VagrantPlugins - module VSphere - module Action - class GetSshInfo - include Util::VimHelpers - - def initialize(app, _env) - @app = app - end - - def call(env) - env[:machine_ssh_info] = get_ssh_info(env[:vSphere_connection], env[:machine]) - @app.call env - end - - private - - def filter_guest_nic(vm, machine) - return vm.guest.ipAddress unless machine.provider_config.real_nic_ip - - interfaces = vm.guest.net.select { |g| g.deviceConfigId > 0 } - ip_addresses = interfaces.map { |i| i.ipConfig.ipAddress.select { |a| a.state == 'preferred' } }.flatten - - return (vm.guest.ipAddress || nil) if ip_addresses.empty? - - fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 - ip_addresses.first.ipAddress - end - - def get_ssh_info(connection, machine) - return nil if machine.id.nil? - - vm = get_vm_by_uuid connection, machine - return nil if vm.nil? - ip_address = filter_guest_nic(vm, machine) - return nil if ip_address.nil? || ip_address.empty? - { - host: ip_address, - port: 22 - } - end - end - end - end -end diff --git a/lib/vSphere/action/get_state.rb b/lib/vSphere/action/get_state.rb deleted file mode 100644 index 357451a5..00000000 --- a/lib/vSphere/action/get_state.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'rbvmomi' -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' - -module VagrantPlugins - module VSphere - module Action - class GetState - include Util::VimHelpers - include Util::VmHelpers - - def initialize(app, _env) - @app = app - end - - def call(env) - env[:machine_state_id] = get_state(env[:vSphere_connection], env[:machine]) - - @app.call env - end - - private - - def get_state(connection, machine) - return :not_created if machine.id.nil? - - vm = get_vm_by_uuid connection, machine - - return :not_created if vm.nil? - - if powered_on?(vm) - :running - else - # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM - :poweroff - end - end - end - end - end -end diff --git a/lib/vSphere/action/is_created.rb b/lib/vSphere/action/is_created.rb index 1a669592..8cbc3a61 100644 --- a/lib/vSphere/action/is_created.rb +++ b/lib/vSphere/action/is_created.rb @@ -7,7 +7,7 @@ def initialize(app, _env) end def call(env) - env[:result] = env[:machine].state.id != :not_created + env[:result] = env[:machine].provider.driver.is_created @app.call env end end diff --git a/lib/vSphere/action/is_running.rb b/lib/vSphere/action/is_running.rb index a2524ae5..3214bcef 100644 --- a/lib/vSphere/action/is_running.rb +++ b/lib/vSphere/action/is_running.rb @@ -7,7 +7,7 @@ def initialize(app, _env) end def call(env) - env[:result] = env[:machine].state.id == :running + env[:result] = env[:machine].provider.driver.is_running @app.call env end end diff --git a/lib/vSphere/action/power_off.rb b/lib/vSphere/action/power_off.rb index 9720f019..d8f6c066 100644 --- a/lib/vSphere/action/power_off.rb +++ b/lib/vSphere/action/power_off.rb @@ -1,35 +1,29 @@ require 'rbvmomi' require 'i18n' -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' module VagrantPlugins module VSphere module Action class PowerOff - include Util::VimHelpers - include Util::VmHelpers - def initialize(app, _env) @app = app end def call(env) - vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] - + driver = env[:machine].provider.driver # If the vm is suspended, we need to turn it on so that we can turn it off. # This may seem counterintuitive, but the vsphere API documentation states # that the Power Off task for a VM will fail if the state is not poweredOn # see: https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.VirtualMachine.html#powerOff - if suspended?(vm) + if driver.suspended? env[:ui].info I18n.t('vsphere.power_on_vm') - power_on_vm(vm) + driver.power_on_vm end # Powering off is a no-op if we can't find the VM or if it is already off - unless vm.nil? || powered_off?(vm) + unless driver.powered_off?.nil? || driver.powered_off? env[:ui].info I18n.t('vsphere.power_off_vm') - power_off_vm(vm) + driver.power_off_vm end @app.call env diff --git a/lib/vSphere/action/power_on.rb b/lib/vSphere/action/power_on.rb index 6d7c546a..f7648024 100644 --- a/lib/vSphere/action/power_on.rb +++ b/lib/vSphere/action/power_on.rb @@ -1,24 +1,17 @@ require 'rbvmomi' require 'i18n' -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' module VagrantPlugins module VSphere module Action class PowerOn - include Util::VimHelpers - include Util::VmHelpers - def initialize(app, _env) @app = app end def call(env) - vm = get_vm_by_uuid env[:vSphere_connection], env[:machine] - env[:ui].info I18n.t('vsphere.power_on_vm') - power_on_vm(vm) + env[:machine].provider.driver.power_on_vm @app.call env end diff --git a/lib/vSphere/action/snapshot_delete.rb b/lib/vSphere/action/snapshot_delete.rb index 445a4928..8fde5cf7 100644 --- a/lib/vSphere/action/snapshot_delete.rb +++ b/lib/vSphere/action/snapshot_delete.rb @@ -1,34 +1,23 @@ -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' - module VagrantPlugins module VSphere module Action class SnapshotDelete - include Util::VimHelpers - include Util::VmHelpers - def initialize(app, _env) @app = app end def call(env) - vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) + snapshot_name = env[:snapshot_name] + + env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.deleting", name: snapshot_name)) - env[:ui].info(I18n.t( - "vagrant.actions.vm.snapshot.deleting", - name: env[:snapshot_name])) - - delete_snapshot(vm, env[:snapshot_name]) do |progress| + env[:machine].provider.driver.delete_snapshot(snapshot_name) do |progress| env[:ui].clear_line env[:ui].report_progress(progress, 100, false) end env[:ui].clear_line - - env[:ui].info(I18n.t( - "vagrant.actions.vm.snapshot.deleted", - name: env[:snapshot_name])) + env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.deleted", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/action/snapshot_list.rb b/lib/vSphere/action/snapshot_list.rb deleted file mode 100644 index 0c3f74cb..00000000 --- a/lib/vSphere/action/snapshot_list.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' - -module VagrantPlugins - module VSphere - module Action - class SnapshotList - include Util::VimHelpers - include Util::VmHelpers - - def initialize(app, _env) - @app = app - end - - def call(env) - vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) - - env[:machine_snapshot_list] = enumerate_snapshots(vm).map(&:name) - - @app.call env - end - end - end - end -end diff --git a/lib/vSphere/action/snapshot_restore.rb b/lib/vSphere/action/snapshot_restore.rb index 2783257b..276f322f 100644 --- a/lib/vSphere/action/snapshot_restore.rb +++ b/lib/vSphere/action/snapshot_restore.rb @@ -1,30 +1,23 @@ -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' - module VagrantPlugins module VSphere module Action class SnapshotRestore - include Util::VimHelpers - include Util::VmHelpers - def initialize(app, _env) @app = app end def call(env) - vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) + snapshot_name = env[:snapshot_name] - env[:ui].info(I18n.t( - "vagrant.actions.vm.snapshot.restoring", - name: env[:snapshot_name])) + env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.restoring", name: snapshot_name)) - restore_snapshot(vm, env[:snapshot_name]) do |progress| + env[:machine].provider.driver.restore_snapshot(snapshot_name) do |progress| env[:ui].clear_line env[:ui].report_progress(progress, 100, false) end env[:ui].clear_line + #env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.restored", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/action/snapshot_save.rb b/lib/vSphere/action/snapshot_save.rb index ec054d69..ea428c24 100644 --- a/lib/vSphere/action/snapshot_save.rb +++ b/lib/vSphere/action/snapshot_save.rb @@ -1,34 +1,24 @@ -require 'vSphere/util/vim_helpers' -require 'vSphere/util/vm_helpers' - module VagrantPlugins module VSphere module Action class SnapshotSave - include Util::VimHelpers - include Util::VmHelpers - def initialize(app, _env) @app = app end def call(env) - vm = get_vm_by_uuid(env[:vSphere_connection], env[:machine]) + snapshot_name = env[:snapshot_name] - env[:ui].info(I18n.t( - "vagrant.actions.vm.snapshot.saving", - name: env[:snapshot_name])) - - create_snapshot(vm, env[:snapshot_name]) do |progress| + env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.saving", name: snapshot_name)) + + env[:machine].provider.driver.create_snapshot(snapshot_name) do |progress| env[:ui].clear_line env[:ui].report_progress(progress, 100, false) end env[:ui].clear_line - - env[:ui].success(I18n.t( - "vagrant.actions.vm.snapshot.saved", - name: env[:snapshot_name])) + env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.saved", name: snapshot_name)) + @app.call env end end diff --git a/lib/vSphere/action/wait_for_ip_address.rb b/lib/vSphere/action/wait_for_ip_address.rb index 2d9b2b63..e90a8688 100644 --- a/lib/vSphere/action/wait_for_ip_address.rb +++ b/lib/vSphere/action/wait_for_ip_address.rb @@ -7,36 +7,43 @@ module Action class WaitForIPAddress def initialize(app, _env) @app = app - @logger = Log4r::Logger.new('vagrant::vsphere::wait_for_ip_addr') end def call(env) - timeout = env[:machine].provider_config.ip_address_timeout + machine = env[:machine] + driver = machine.provider.driver + timeout = machine.provider_config.ip_address_timeout env[:ui].output('Waiting for the machine to report its IP address...') env[:ui].detail("Timeout: #{timeout} seconds") guest_ip = nil + + fail Errors::VSphereError, :wait_for_ip_address_timeout unless driver.is_created + Timeout.timeout(timeout) do loop do # If a ctrl-c came through, break out return if env[:interrupted] - guest_ip = nil - - if env[:machine].state.id == :running - ssh_info = env[:machine].ssh_info - guest_ip = ssh_info[:host] unless ssh_info.nil? - end + if driver.is_running + ssh_info = driver.ssh_info - if guest_ip - begin - IPAddr.new(guest_ip) - break - rescue IPAddr::InvalidAddressError - # Ignore, continue looking. - @logger.warn("Invalid IP address returned: #{guest_ip}") + if ssh_info.nil? + env[:ui].info("Waiting for ip address") + else + guest_ip = ssh_info[:host] + + begin + IPAddr.new(guest_ip) + break + rescue IPAddr::InvalidAddressError + # Ignore, continue looking. + env[:ui].warn("Invalid IP address returned: #{guest_ip}") + end end + else + env[:ui].warn("Machine is not running") end sleep 1 @@ -50,7 +57,7 @@ def call(env) @app.call(env) rescue Timeout::Error - raise Errors::VSphereError, :wait_for_ip_address_timeout + fail Errors::VSphereError, :wait_for_ip_address_timeout end end end diff --git a/lib/vSphere/cap/snapshot_list.rb b/lib/vSphere/cap/snapshot_list.rb index cb44ceee..54d054d0 100644 --- a/lib/vSphere/cap/snapshot_list.rb +++ b/lib/vSphere/cap/snapshot_list.rb @@ -6,8 +6,7 @@ module SnapshotList # # @return [Array] Snapshot Name def self.snapshot_list(machine) - env = machine.action(:snapshot_list, lock: false) - env[:machine_snapshot_list] + machine.driver.snapshot_list end end end diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb new file mode 100644 index 00000000..75e1e21b --- /dev/null +++ b/lib/vSphere/driver.rb @@ -0,0 +1,643 @@ +require 'log4r' +require 'rbvmomi' + +module VagrantPlugins + module VSphere + module VmState + POWERED_ON = 'poweredOn' + POWERED_OFF = 'poweredOff' + SUSPENDED = 'suspended' + end + + class Driver + attr_reader :logger + attr_reader :machine + + def initialize(machine) + @logger = Log4r::Logger.new("vagrant::provider::vsphere::driver") + @machine = machine + end + + def connection + raise "connection be called from a code block!" if !block_given? + + begin + config = @machine.provider_config + + current_connection = RbVmomi::VIM.connect host: config.host, + user: config.user, password: config.password, + insecure: config.insecure, proxyHost: config.proxy_host, + proxyPort: config.proxy_port + + yield current_connection + rescue + raise + ensure + current_connection.close if current_connection + end + end + + def ssh_info + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return nil if vm.nil? + + ip_address = filter_guest_nic(vm, @machine) + return nil if ip_address.nil? || ip_address.empty? + { + host: ip_address, + port: 22 + } + end + end + + def state + return :not_created if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + return :not_created if vm.nil? + + if powered_on? + :running + else + # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM + :poweroff + end + end + end + + def power_on_vm + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering on vm #{@machine.id}") + vm.PowerOnVM_Task.wait_for_completion + @logger.info("Finished powering on vm #{@machine.id}") + end + end + + def power_off_vm + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering off vm #{@machine.id}") + vm.PowerOffVM_Task.wait_for_completion + @logger.info("Finished powering off vm #{@machine.id}") + end + end + + def get_vm_state + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState + end + end + + def powered_on? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_ON) + end + end + + def powered_off? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_OFF) + end + end + + def suspended? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::SUSPENDED) + end + end + + def clone(root_path) + config = machine.provider_config + connection do |conn| + name = get_name @machine, config, root_path + dc = get_datacenter conn, @machine + template = dc.find_vm config.template_name + fail Errors::VSphereError, :'missing_template' if template.nil? + vm_base_folder = get_vm_base_folder dc, template, config + fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? + + begin + # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf + ds = get_datastore dc, @machine + fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) + + location = get_location ds, dc, @machine, template + + spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false + spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec + customization_info = get_customization_spec_info_by_name conn, @machine + spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? + add_custom_address_type(template, spec, config.addressType) unless config.addressType.nil? + add_custom_mac(template, spec, config.mac) unless config.mac.nil? + add_custom_vlan(template, dc, spec, config.vlan) unless config.vlan.nil? + add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? + add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? + add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? + add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? + add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? + add_custom_notes(spec, config.notes) unless config.notes.nil? + + if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) + + storage_mgr = conn.serviceContent.storageResourceManager + pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) + # TODO: May want to add option on type? + storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) + + @logger.info(I18n.t('vsphere.requesting_sdrs_recommendation')) + @logger.info(" -- DatastoreCluster: #{ds.name}") + @logger.info(" -- Template VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) + + recommendation = result.recommendations[0] + key = recommendation.key ||= '' + if key == '' + fail Errors::VSphereError, :missing_datastore_recommendation + end + + @logger.info(I18n.t('vsphere.creating_cloned_vm_sdrs')) + @logger.info(" -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}") + + @logger.info("Start cloning vm #{@machine.id}") + task = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]) + + apply_sr_result = nil + if block_given? + apply_sr_result = task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + apply_sr_result = task.wait_for_completion + end + @logger.info("Finished cloning vm #{@machine.id}") + + new_vm = apply_sr_result.vm + else + @logger.info(I18n.t('vsphere.creating_cloned_vm')) + @logger.info(" -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + @logger.info("Start cloning vm #{@machine.id}") + task = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec) + new_vm = nil + if block_given? + new_vm = task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + new_vm = task.wait_for_completion + end + @logger.info("Finished cloning vm #{@machine.id}") + + config.custom_attributes.each do |k, v| + new_vm.setCustomValue(key: k, value: v) + end + end + rescue Errors::VSphereError + raise + rescue StandardError => e + raise Errors::VSphereError.new, e.message + end + + # TODO: handle interrupted status in the environment, should the vm be destroyed? + @machine.id = new_vm.config.uuid + end + end + + def destroy + return nil if @machine.id.nil? + return nil unless is_created + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + task = vm.Destroy_Task + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished destroying vm #{@machine.id}") + end + + @machine.id = nil + end + + def is_created + return false if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return false if vm.nil? + end + + true + end + + def is_running + state == :running + end + + def snapshot_list + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + snapshots = enumerate_snapshots(vm).map(&:name) + @logger.info("Finished destroying vm #{@machine.id}") + return snapshots + end + end + + def delete_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + + # No snapshot matching "name" + return nil if snapshot.nil? + + task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) + + @logger.info("Start deleting snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished deleting snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + def restore_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + + # No snapshot matching "name" + return nil if snapshot.nil? + + task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) + + @logger.info("Start restoring snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished restoring snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + def create_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + task = vm.CreateSnapshot_Task( + name: name, + memory: false, + quiesce: false) + + @logger.info("Start creating snapshot #{snapshot_name} on vm #{@machine.id}") + + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + + @logger.info("Finished creating snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + private + + # Enumerate VM snapshot tree + # + # This method returns an enumerator that performs a depth-first walk + # of the VM snapshot grap and yields each VirtualMachineSnapshotTree + # node. + # + # @param vm [RbVmomi::VIM::VirtualMachine] + # + # @return [Enumerator] + def enumerate_snapshots(vm) + snapshot_info = vm.snapshot + + if snapshot_info.nil? + snapshot_root = [] + else + snapshot_root = snapshot_info.rootSnapshotList + end + + recursor = lambda do |snapshot_list| + Enumerator.new do |yielder| + snapshot_list.each do |s| + # Yield the current VirtualMachineSnapshotTree object + yielder.yield s + + # Recurse into child VirtualMachineSnapshotTree objects + children = recursor.call(s.childSnapshotList) + loop do + yielder.yield children.next + end + end + end + end + + recursor.call(snapshot_root) + end + + def filter_guest_nic(vm, machine) + return vm.guest.ipAddress unless machine.provider_config.real_nic_ip + ip_addresses = vm.guest.net.select { |g| g.deviceConfigId > 0 }.map { |g| g.ipAddress[0] } + fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 + ip_addresses.first + end + + def get_datacenter(connection, machine) + connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) + end + + def get_vm_by_uuid(connection, machine) + get_datacenter(connection, machine).vmFolder.findByUuid machine.id + end + + def get_resource_pool(datacenter, machine) + rp = get_compute_resource(datacenter, machine) + + resource_pool_name = machine.provider_config.resource_pool_name || '' + + entity_array = resource_pool_name.split('/') + entity_array.each do |entity_array_item| + next if entity_array_item.empty? + if rp.is_a? RbVmomi::VIM::Folder + rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource + rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ResourcePool + rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ComputeResource + rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) + else + fail Errors::VSphereError, :missing_resource_pool + end + end + rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) + rp + end + + def get_compute_resource(datacenter, machine) + cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) + fail Errors::VSphereError, :missing_compute_resource if cr.nil? + cr + end + + def find_clustercompute_or_compute_resource(datacenter, path) + if path.is_a? String + es = path.split('/').reject(&:empty?) + elsif path.is_a? Enumerable + es = path + else + fail "unexpected path class #{path.class}" + end + return datacenter.hostFolder if es.empty? + final = es.pop + + p = es.inject(datacenter.hostFolder) do |f, e| + f.find(e, RbVmomi::VIM::Folder) || return + end + + begin + if (x = p.find(final, RbVmomi::VIM::ComputeResource)) + x + elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) + x + end + rescue Exception + # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead + x = p.childEntity.find { |x2| x2.name == final } + if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) + x + else + puts 'ex unknown type ' + x.to_json + nil + end + end + end + + def get_customization_spec_info_by_name(connection, machine) + name = machine.provider_config.customization_spec_name + return if name.nil? || name.empty? + + manager = connection.serviceContent.customizationSpecManager + fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? + + spec = manager.GetCustomizationSpec(name: name) + fail Errors::VSphereError, :missing_configuration_spec if spec.nil? + + spec + end + + def get_datastore(datacenter, machine) + name = machine.provider_config.data_store_name + return if name.nil? || name.empty? + + # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) + datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) + end + + def get_network_by_name(dc, name) + dc.network.find { |f| f.name == name } || fail(Errors::VSphereError, :missing_vlan) + end + + #Cloning + def get_customization_spec(machine, spec_info) + customization_spec = spec_info.spec.clone + + # find all the configured private networks + private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } + return customization_spec if private_networks.nil? + + # make sure we have enough NIC settings to override with the private network settings + fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length + + # assign the private network IP to the NIC + private_networks.each_index do |idx| + customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] + end + + customization_spec + end + + def get_location(datastore, dc, machine, template) + if machine.provider_config.linked_clone + # The API for linked clones is quite strange. We can't create a linked + # straight from any VM. The disks of the VM for which we can create a + # linked clone need to be read-only and thus VC demands that the VM we + # are cloning from uses delta-disks. Only then it will allow us to + # share the base disk. + # + # Thus, this code first create a delta disk on top of the base disk for + # the to-be-cloned VM, if delta disks aren't used already. + disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) + disks.select { |disk| disk.backing.parent.nil? }.each do |disk| + spec = { + deviceChange: [ + { + operation: :remove, + device: disk + }, + { + operation: :add, + fileOperation: :create, + device: disk.dup.tap do |new_disk| + new_disk.backing = new_disk.backing.dup + new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" + new_disk.backing.parent = disk.backing + end + } + ] + } + template.ReconfigVM_Task(spec: spec).wait_for_completion + end + + location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) + elsif datastore.is_a? RbVmomi::VIM::StoragePod + location = RbVmomi::VIM.VirtualMachineRelocateSpec + else + location = RbVmomi::VIM.VirtualMachineRelocateSpec + + location[:datastore] = datastore unless datastore.nil? + end + location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm + location + end + + def get_name(machine, config, root_path) + return config.name unless config.name.nil? + + prefix = "#{root_path.basename}_#{machine.name}" + prefix.gsub!(/[^-a-z0-9_\.]/i, '') + # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs + prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" + end + + def get_vm_base_folder(dc, template, config) + if config.vm_base_path.nil? + template.parent + else + dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) + end + end + + def modify_network_card(template, spec) + spec[:config][:deviceChange] ||= [] + @card ||= template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first + + fail Errors::VSphereError, :missing_network_card if @card.nil? + + yield(@card) + + dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @card, operation: 'edit') + spec[:config][:deviceChange].push dev_spec + spec[:config][:deviceChange].uniq! + end + + def add_custom_address_type(template, spec, addressType) + spec[:config][:deviceChange] = [] + config = template.config + card = config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first || fail(Errors::VSphereError, :missing_network_card) + card.addressType = addressType + card_spec = { :deviceChange => [{ :operation => :edit, :device => card }] } + template.ReconfigVM_Task(:spec => card_spec).wait_for_completion + end + + def add_custom_mac(template, spec, mac) + modify_network_card(template, spec) do |card| + card.macAddress = mac + end + end + + def add_custom_vlan(template, dc, spec, vlan) + network = get_network_by_name(dc, vlan) + + modify_network_card(template, spec) do |card| + begin + switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) + card.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) + rescue + # not connected to a distibuted switch? + card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) + end + end + end + + def add_custom_memory(spec, memory_mb) + spec[:config][:memoryMB] = Integer(memory_mb) + end + + def add_custom_cpu(spec, cpu_count) + spec[:config][:numCPUs] = Integer(cpu_count) + end + + def add_custom_cpu_reservation(spec, cpu_reservation) + spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) + end + + def add_custom_mem_reservation(spec, mem_reservation) + spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) + end + + def add_custom_extra_config(spec, extra_config = {}) + return if extra_config.empty? + + # extraConfig must be an array of hashes with `key` and `value` + # entries. + spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } + end + + def add_custom_notes(spec, notes) + spec[:config][:annotation] = notes + end + end + end +end \ No newline at end of file diff --git a/lib/vSphere/provider.rb b/lib/vSphere/provider.rb index f30c48a4..10ff3e1a 100644 --- a/lib/vSphere/provider.rb +++ b/lib/vSphere/provider.rb @@ -1,10 +1,19 @@ +require 'log4r' require 'vagrant' +require_relative 'driver' module VagrantPlugins module VSphere class Provider < Vagrant.plugin('2', :provider) + attr_reader :driver + def initialize(machine) + @logger = Log4r::Logger.new('vagrant::provider::vsphere') @machine = machine + + # This method will load in our driver, so we call it now to + # initialize it. + machine_id_changed end def action(name) @@ -13,20 +22,38 @@ def action(name) nil end + # If the machine ID changed, then we need to rebuild our underlying + # driver. + def machine_id_changed + id = @machine.id + @logger.debug("Instantiating the driver for machine ID: #{@machine.id.inspect}") + @driver = VagrantPlugins::VSphere::Driver.new(@machine) + nil + end + + def driver + return @driver if @driver + @driver = VagrantPlugins::VSphere::Driver.new(@machine) + end + def ssh_info - env = @machine.action('get_ssh_info', lock: false) - env[:machine_ssh_info] + driver.ssh_info end def state - env = @machine.action('get_state', lock: false) + # Determine the ID of the state here. + state_id = @driver.state - state_id = env[:machine_state_id] + # Translate into short/long descriptions + short = state_id.to_s.gsub('_', ' ') + long = I18n.t("vagrant_vsphere.commands.status.#{state_id}") - short = "vagrant_vsphere.states.short_#{state_id}" - long = "vagrant_vsphere.states.long_#{state_id}" + # If machine is not created, then specify the special ID flag + if state_id == :not_created + state_id = Vagrant::MachineState::NOT_CREATED_ID + end - # Return the MachineState object + # Return the state Vagrant::MachineState.new(state_id, short, long) end @@ -36,4 +63,4 @@ def to_s end end end -end +end \ No newline at end of file diff --git a/lib/vSphere/util/vim_helpers.rb b/lib/vSphere/util/vim_helpers.rb deleted file mode 100644 index d23e3bb4..00000000 --- a/lib/vSphere/util/vim_helpers.rb +++ /dev/null @@ -1,105 +0,0 @@ -require 'rbvmomi' - -module VagrantPlugins - module VSphere - module Util - module VimHelpers - def get_datacenter(connection, machine) - connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) - end - - def get_vm_by_uuid(connection, machine) - get_datacenter(connection, machine).vmFolder.findByUuid machine.id - end - - def get_resource_pool(datacenter, machine) - rp = get_compute_resource(datacenter, machine) - - resource_pool_name = machine.provider_config.resource_pool_name || '' - - entity_array = resource_pool_name.split('/') - entity_array.each do |entity_array_item| - next if entity_array_item.empty? - if rp.is_a? RbVmomi::VIM::Folder - rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource - rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ResourcePool - rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ComputeResource - rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) - else - fail Errors::VSphereError, :missing_resource_pool - end - end - rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) - rp - end - - def get_compute_resource(datacenter, machine) - cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) - fail Errors::VSphereError, :missing_compute_resource if cr.nil? - cr - end - - def find_clustercompute_or_compute_resource(datacenter, path) - if path.is_a? String - es = path.split('/').reject(&:empty?) - elsif path.is_a? Enumerable - es = path - else - fail "unexpected path class #{path.class}" - end - return datacenter.hostFolder if es.empty? - final = es.pop - - p = es.inject(datacenter.hostFolder) do |f, e| - f.find(e, RbVmomi::VIM::Folder) || return - end - - begin - if (x = p.find(final, RbVmomi::VIM::ComputeResource)) - x - elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) - x - end - rescue Exception - # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead - x = p.childEntity.find { |x2| x2.name == final } - if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) - x - else - puts 'ex unknown type ' + x.to_json - nil - end - end - end - - def get_customization_spec_info_by_name(connection, machine) - name = machine.provider_config.customization_spec_name - return if name.nil? || name.empty? - - manager = connection.serviceContent.customizationSpecManager - fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? - - spec = manager.GetCustomizationSpec(name: name) - fail Errors::VSphereError, :missing_configuration_spec if spec.nil? - - spec - end - - def get_datastore(datacenter, machine) - name = machine.provider_config.data_store_name - return if name.nil? || name.empty? - - # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) - datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) - end - - def get_network_by_name(dc, name) - dc.network.find { |f| f.name == name } || fail(Errors::VSphereError, :missing_vlan) - end - end - end - end -end diff --git a/lib/vSphere/util/vm_helpers.rb b/lib/vSphere/util/vm_helpers.rb deleted file mode 100644 index d4ca9662..00000000 --- a/lib/vSphere/util/vm_helpers.rb +++ /dev/null @@ -1,160 +0,0 @@ -require 'rbvmomi' - -module VagrantPlugins - module VSphere - module Util - module VmState - POWERED_ON = 'poweredOn' - POWERED_OFF = 'poweredOff' - SUSPENDED = 'suspended' - end - - module VmHelpers - def power_on_vm(vm) - vm.PowerOnVM_Task.wait_for_completion - end - - def power_off_vm(vm) - vm.PowerOffVM_Task.wait_for_completion - end - - def get_vm_state(vm) - vm.runtime.powerState - end - - def powered_on?(vm) - get_vm_state(vm).eql?(VmState::POWERED_ON) - end - - def powered_off?(vm) - get_vm_state(vm).eql?(VmState::POWERED_OFF) - end - - def suspended?(vm) - get_vm_state(vm).eql?(VmState::SUSPENDED) - end - - # Enumerate VM snapshot tree - # - # This method returns an enumerator that performs a depth-first walk - # of the VM snapshot grap and yields each VirtualMachineSnapshotTree - # node. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # - # @return [Enumerator] - def enumerate_snapshots(vm) - snapshot_info = vm.snapshot - - if snapshot_info.nil? - snapshot_root = [] - else - snapshot_root = snapshot_info.rootSnapshotList - end - - recursor = lambda do |snapshot_list| - Enumerator.new do |yielder| - snapshot_list.each do |s| - # Yield the current VirtualMachineSnapshotTree object - yielder.yield s - - # Recurse into child VirtualMachineSnapshotTree objects - children = recursor.call(s.childSnapshotList) - loop do - yielder.yield children.next - end - end - end - end - - recursor.call(snapshot_root) - end - - # Create a named snapshot on a given VM - # - # This method creates a named snapshot on the given VM. This method - # blocks until the snapshot creation task is complete. An optional - # block can be passed which is used to report progress. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # @param name [String] - # @yield [Integer] Percentage complete as an integer. Called multiple - # times. - # - # @return [void] - def create_snapshot(vm, name) - task = vm.CreateSnapshot_Task( - name: name, - memory: false, - quiesce: false) - - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - end - - # Delete a named snapshot on a given VM - # - # This method deletes a named snapshot on the given VM. This method - # blocks until the snapshot deletion task is complete. An optional - # block can be passed which is used to report progress. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # @param name [String] - # @yield [Integer] Percentage complete as an integer. Called multiple - # times. - # - # @return [void] - def delete_snapshot(vm, name) - snapshot = enumerate_snapshots(vm).find { |s| s.name == name } - - # No snapshot matching "name" - return nil if snapshot.nil? - - task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) - - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - end - - # Restore a VM to a named snapshot - # - # This method restores a VM to the named snapshot state. This method - # blocks until the restoration task is complete. An optional block can - # be passed which is used to report progress. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # @param name [String] - # @yield [Integer] Percentage complete as an integer. Called multiple - # times. - # - # @return [void] - def restore_snapshot(vm, name) - snapshot = enumerate_snapshots(vm).find { |s| s.name == name } - - # No snapshot matching "name" - return nil if snapshot.nil? - - task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) - - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - end - end - end - end -end From 5ba6e466e2d8d159969de2092db67b4949e4ea78 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 7 Nov 2016 16:12:54 +0000 Subject: [PATCH 02/39] Provide mechanism to configure multiple network cards Update documentation for multiple network cards --- README.md | 71 +-- lib/vSphere/config.rb | 35 +- lib/vSphere/driver.rb | 1069 ++++++++++++++++++++++------------------- 3 files changed, 637 insertions(+), 538 deletions(-) diff --git a/README.md b/README.md index 7e96acec..8f656669 100644 --- a/README.md +++ b/README.md @@ -100,60 +100,65 @@ and `ssh`. This provider has the following settings, all are required unless noted: -* `host` - IP or name for the vSphere API -* `insecure` - _Optional_ verify SSL certificate from the host -* `user` - user name for connecting to vSphere -* `password` - password for connecting to vSphere. If no value is given, or the +* `host` - string - IP or name for the vSphere API +* `insecure` - _Optional_ boolean - verify SSL certificate from the host +* `user` - string - user name for connecting to vSphere +* `password` - string - password for connecting to vSphere. If no value is given, or the value is set to `:ask`, the user will be prompted to enter the password on each invocation. -* `data_center_name` - _Optional_ datacenter containing the computed resource, +* `data_center_name` - _Optional_ string - datacenter containing the computed resource, the template and where the new VM will be created, if not specified the first datacenter found will be used -* `compute_resource_name` - _Required if cloning from template_ the name of the +* `compute_resource_name` - string - _Required if cloning from template_ the name of the host or cluster containing the resource pool for the new VM -* `resource_pool_name` - the resource pool for the new VM. If not supplied, and +* `resource_pool_name` - string - the resource pool for the new VM. If not supplied, and cloning from a template, uses the root resource pool -* `clone_from_vm` - _Optional_ use a virtual machine instead of a template as +* `clone_from_vm` - _Optional_ string - use a virtual machine instead of a template as the source for the cloning operation -* `template_name` - the VM or VM template to clone (including the full folder path) -* `vm_base_path` - _Optional_ path to folder where new VM should be created, if +* `template_name` - string - the VM or VM template to clone (including the full folder path) +* `vm_base_path` - _Optional_ string - path to folder where new VM should be created, if not specified template's parent folder will be used -* `name` - _Optional_ name of the new VM, if missing the name will be auto +* `name` - _Optional_ string - name of the new VM, if missing the name will be auto generated -* `customization_spec_name` - _Optional_ customization spec for the new VM -* `data_store_name` - _Optional_ the datastore where the VM will be located -* `linked_clone` - _Optional_ link the cloned VM to the parent to share virtual +* `customization_spec_name` - _Optional_ string - customization spec for the new VM +* `data_store_name` - _Optional_ string - the datastore where the VM will be located +* `linked_clone` - _Optional_ string - link the cloned VM to the parent to share virtual disks -* `proxy_host` - _Optional_ proxy host name for connecting to vSphere via proxy -* `proxy_port` - _Optional_ proxy port number for connecting to vSphere via +* `proxy_host` - _Optional_ string - proxy host name for connecting to vSphere via proxy +* `proxy_port` - _Optional_ integer - proxy port number for connecting to vSphere via proxy -* `vlan` - _Optional_ vlan to connect the first NIC to -* `memory_mb` - _Optional_ Configure the amount of memory (in MB) for the new VM -* `cpu_count` - _Optional_ Configure the number of CPUs for the new VM -* `mac` - _Optional_ Used to set the mac address of the new VM -* `cpu_reservation` - _Optional_ Configure the CPU time (in MHz) to reserve for this VM -* `mem_reservation` - _Optional_ Configure the memory (in MB) to reserve for this VM -* `addressType` - _Optional_ Configure the address type of the - [vSphere Virtual Ethernet Card](https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.vm.device.VirtualEthernetCard.html) -* `custom_attribute` - _Optional_ Add a +* `memory_mb` - _Optional_ integer - Configure the amount of memory (in MB) for the new VM +* `cpu_count` - _Optional_ integer - Configure the number of CPUs for the new VM +* `cpu_reservation` - _Optional_ integer - Configure the CPU time (in MHz) to reserve for this VM +* `mem_reservation` - _Optional_ integer - Configure the memory (in MB) to reserve for this VM +* `custom_attribute` - _Optional_ hash - Add a [custom attribute](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB4QFjAAahUKEwiWwbWX59jHAhVBC5IKHa3HAEU&url=http%3A%2F%2Fpubs.vmware.com%2Fvsphere-51%2Ftopic%2Fcom.vmware.vsphere.vcenterhost.doc%2FGUID-25244732-D473-4857-A471-579257B6D95F.html&usg=AFQjCNGTSl4cauFrflUJpBeTBb0Yv7R13g&sig2=a9he6W2qVvBSZ5lCiXnENA) to the VM upon creation. This method takes a key/value pair, e.g. `vsphere.custom_attribute('timestamp', Time.now.to_s)`, and may be called multiple times to set different attributes. -* `extra_config` - _Optional_ A hash of extra configuration values to add to +* `extra_config` - _Optional_ hash - A hash of extra configuration values to add to the VM during creation. These are of the form `{'guestinfo.some.variable' => 'somevalue'}`, where the key must start with `guestinfo.`. VMs with VWware Tools installed can retrieve the value of these variables using the `vmtoolsd` command: `vmtoolsd --cmd 'info-get guestinfo.some.variable'`. -* `notes` - _Optional_ Add arbitrary notes to the VM -* `real_nic_ip` - _Optional_ true/false - Enable logic that forces the acquisition of the ssh IP address +* `notes` - _Optional_ string - Add arbitrary notes to the VM +* `real_nic_ip` - _Optional_ boolean - Enable logic that forces the acquisition of the ssh IP address for a target VM to be retrieved from the list of vm adapters on the host and filtered for a single legitimate adapter with a defined interface. An error will be raised if this filter is enabled and multiple valid adapters exist on a host. -* `ip_address_timeout` - _Optional_ Maximum number of seconds to wait while an - IP address is obtained -* `wait_for_sysprep` - _Optional_ Boolean. Enable waiting for Windows machines to reboot - during the sysprep process - ([#199](https://github.com/nsidc/vagrant-vsphere/pull/199)). Defaults to `false`. +* `destroy_unused_network_interfaces` - _Optional_ boolean - should network cards that have not been configured + explicitly, be deleted. If set to false then existing network cards are left alone. +* `network_adapter` - Array of network card configuration + +Network card configuration: +* `slot` - integer - zero based array of the card index +* `allowGuestControl` - _Optional_ boolean - Configure the address type of the +* `connected` - _Optional_ boolean - is the vm network card connected to the network +* `startConnected` - _Optional_ boolean - When VM is turned on should the vm network card be connected to the network +* `vlan` - _Optional_ string - vlan to connect the network card to +* `addressType` - _Optional_ - Configure the address type of the + [vSphere Virtual Ethernet Card](https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.vm.device.VirtualEthernetCard.html) +* `macAddress` - _Optional_ string - Used to set the mac address of the new VM +* `wakeOnLanEnabled` - _Optional_ boolean - should vm turn on when magic packet is received on network card ### Cloning from a VM rather than a template diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 19d3efb5..3ab84287 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -20,36 +20,47 @@ class Config < Vagrant.plugin('2', :config) attr_accessor :linked_clone attr_accessor :proxy_host attr_accessor :proxy_port - attr_accessor :vlan - attr_accessor :addressType - attr_accessor :mac attr_accessor :memory_mb attr_accessor :cpu_count attr_accessor :cpu_reservation attr_accessor :mem_reservation attr_accessor :extra_config - attr_accessor :real_nic_ip attr_accessor :notes - attr_accessor :wait_for_sysprep - attr_reader :custom_attributes + attr_accessor :real_nic_ip + + attr_accessor :destroy_unused_network_interfaces + attr_reader :network_adapters + attr_reader :custom_attributes def initialize @ip_address_timeout = UNSET_VALUE - @wait_for_sysprep = UNSET_VALUE + @destroy_unused_network_interfaces = UNSET_VALUE + @network_adapters = {} @custom_attributes = {} @extra_config = {} end - def finalize! - @ip_address_timeout = 240 if @ip_address_timeout == UNSET_VALUE - @wait_for_sysprep = false if @wait_for_sysprep == UNSET_VALUE - end - def custom_attribute(key, value) @custom_attributes[key.to_sym] = value end + #attr_accessor :allowGuestControl + #attr_accessor :connected + #attr_accessor :startConnected + + #attr_accessor :vlan + #attr_accessor :addressType + #attr_accessor :macAddress + #attr_accessor :wakeOnLanEnabled + def network_adapter(slot, **opts) + @network_adapters[slot] = opts + end + + def finalize! + @ip_address_timeout = 240 if @ip_address_timeout == UNSET_VALUE + end + def validate(machine) errors = _detected_errors diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 75e1e21b..55604e29 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -2,642 +2,725 @@ require 'rbvmomi' module VagrantPlugins - module VSphere - module VmState - POWERED_ON = 'poweredOn' - POWERED_OFF = 'poweredOff' - SUSPENDED = 'suspended' + module VSphere + module VmState + POWERED_ON = 'poweredOn' + POWERED_OFF = 'poweredOff' + SUSPENDED = 'suspended' + end + + class Driver + attr_reader :logger + attr_reader :machine + + def initialize(machine) + @logger = Log4r::Logger.new("vagrant::provider::vsphere::driver") + @machine = machine end - class Driver - attr_reader :logger - attr_reader :machine + def connection + raise "connection be called from a code block!" if !block_given? - def initialize(machine) - @logger = Log4r::Logger.new("vagrant::provider::vsphere::driver") - @machine = machine - end - - def connection - raise "connection be called from a code block!" if !block_given? + begin + config = @machine.provider_config - begin - config = @machine.provider_config + current_connection = RbVmomi::VIM.connect host: config.host, + user: config.user, password: config.password, + insecure: config.insecure, proxyHost: config.proxy_host, + proxyPort: config.proxy_port - current_connection = RbVmomi::VIM.connect host: config.host, - user: config.user, password: config.password, - insecure: config.insecure, proxyHost: config.proxy_host, - proxyPort: config.proxy_port - - yield current_connection - rescue - raise - ensure - current_connection.close if current_connection - end + yield current_connection + rescue + raise + ensure + current_connection.close if current_connection end + end - def ssh_info - return nil if @machine.id.nil? + def ssh_info + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - return nil if vm.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return nil if vm.nil? - ip_address = filter_guest_nic(vm, @machine) - return nil if ip_address.nil? || ip_address.empty? - { - host: ip_address, - port: 22 - } - end + ip_address = filter_guest_nic(vm, @machine) + return nil if ip_address.nil? || ip_address.empty? + { + host: ip_address, + port: 22 + } end + end - def state - return :not_created if @machine.id.nil? + def state + return :not_created if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine + connection do |conn| + vm = get_vm_by_uuid conn, @machine - return :not_created if vm.nil? + return :not_created if vm.nil? - if powered_on? - :running - else - # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM - :poweroff - end + if powered_on? + :running + else + # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM + :poweroff end end + end - def power_on_vm - return nil if @machine.id.nil? + def power_on_vm + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start powering on vm #{@machine.id}") - vm.PowerOnVM_Task.wait_for_completion - @logger.info("Finished powering on vm #{@machine.id}") - end + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering on vm #{@machine.id}") + vm.PowerOnVM_Task.wait_for_completion + @logger.info("Finished powering on vm #{@machine.id}") end + end - def power_off_vm - return nil if @machine.id.nil? + def power_off_vm + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start powering off vm #{@machine.id}") - vm.PowerOffVM_Task.wait_for_completion - @logger.info("Finished powering off vm #{@machine.id}") - end + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering off vm #{@machine.id}") + vm.PowerOffVM_Task.wait_for_completion + @logger.info("Finished powering off vm #{@machine.id}") end + end - def get_vm_state - return nil if @machine.id.nil? + def get_vm_state + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState - end + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState end + end - def powered_on? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::POWERED_ON) - end + def powered_on? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_ON) end + end - def powered_off? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::POWERED_OFF) - end + def powered_off? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_OFF) end + end - def suspended? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::SUSPENDED) - end + def suspended? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::SUSPENDED) end + end - def clone(root_path) - config = machine.provider_config - connection do |conn| - name = get_name @machine, config, root_path - dc = get_datacenter conn, @machine - template = dc.find_vm config.template_name - fail Errors::VSphereError, :'missing_template' if template.nil? - vm_base_folder = get_vm_base_folder dc, template, config - fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? - - begin - # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf - ds = get_datastore dc, @machine - fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) - - location = get_location ds, dc, @machine, template - - spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false - spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec - customization_info = get_customization_spec_info_by_name conn, @machine - spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? - add_custom_address_type(template, spec, config.addressType) unless config.addressType.nil? - add_custom_mac(template, spec, config.mac) unless config.mac.nil? - add_custom_vlan(template, dc, spec, config.vlan) unless config.vlan.nil? - add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? - add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? - add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? - add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? - add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? - add_custom_notes(spec, config.notes) unless config.notes.nil? - - if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) - - storage_mgr = conn.serviceContent.storageResourceManager - pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) - # TODO: May want to add option on type? - storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) - - @logger.info(I18n.t('vsphere.requesting_sdrs_recommendation')) - @logger.info(" -- DatastoreCluster: #{ds.name}") - @logger.info(" -- Template VM: #{template.pretty_path}") - @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") - - result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) - - recommendation = result.recommendations[0] - key = recommendation.key ||= '' - if key == '' - fail Errors::VSphereError, :missing_datastore_recommendation - end + def clone(root_path) + config = machine.provider_config + connection do |conn| + name = get_name @machine, config, root_path + dc = get_datacenter conn, @machine + template = dc.find_vm config.template_name + fail Errors::VSphereError, :'missing_template' if template.nil? + vm_base_folder = get_vm_base_folder dc, template, config + fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? - @logger.info(I18n.t('vsphere.creating_cloned_vm_sdrs')) - @logger.info(" -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}") + begin + # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf + ds = get_datastore dc, @machine + fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) + + location = get_location ds, dc, @machine, template + + spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false + spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec + customization_info = get_customization_spec_info_by_name conn, @machine + spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? + + spec = configure_network_cards(spec, config, template) + + add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? + add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? + add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? + add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? + add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? + add_custom_notes(spec, config.notes) unless config.notes.nil? + + if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) + + storage_mgr = conn.serviceContent.storageResourceManager + pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) + # TODO: May want to add option on type? + storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) + + @logger.info(I18n.t('vsphere.requesting_sdrs_recommendation')) + @logger.info(" -- DatastoreCluster: #{ds.name}") + @logger.info(" -- Template VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) + + recommendation = result.recommendations[0] + key = recommendation.key ||= '' + if key == '' + fail Errors::VSphereError, :missing_datastore_recommendation + end - @logger.info("Start cloning vm #{@machine.id}") - task = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]) + @logger.info(I18n.t('vsphere.creating_cloned_vm_sdrs')) + @logger.info(" -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}") - apply_sr_result = nil - if block_given? - apply_sr_result = task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - apply_sr_result = task.wait_for_completion - end - @logger.info("Finished cloning vm #{@machine.id}") + @logger.info("Start cloning vm #{@machine.id}") + task = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]) - new_vm = apply_sr_result.vm - else - @logger.info(I18n.t('vsphere.creating_cloned_vm')) - @logger.info(" -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}") - @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") - - @logger.info("Start cloning vm #{@machine.id}") - task = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec) - new_vm = nil - if block_given? - new_vm = task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - new_vm = task.wait_for_completion + apply_sr_result = nil + if block_given? + apply_sr_result = task.wait_for_progress do |progress| + yield progress unless progress.nil? end - @logger.info("Finished cloning vm #{@machine.id}") + else + apply_sr_result = task.wait_for_completion + end + @logger.info("Finished cloning vm #{@machine.id}") - config.custom_attributes.each do |k, v| - new_vm.setCustomValue(key: k, value: v) + new_vm = apply_sr_result.vm + else + @logger.info(I18n.t('vsphere.creating_cloned_vm')) + @logger.info(" -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + @logger.info("Start cloning vm #{@machine.id}") + task = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec) + new_vm = nil + if block_given? + new_vm = task.wait_for_progress do |progress| + yield progress unless progress.nil? end + else + new_vm = task.wait_for_completion end - rescue Errors::VSphereError - raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message - end + @logger.info("Finished cloning vm #{@machine.id}") - # TODO: handle interrupted status in the environment, should the vm be destroyed? - @machine.id = new_vm.config.uuid + config.custom_attributes.each do |k, v| + new_vm.setCustomValue(key: k, value: v) + end + end + rescue Errors::VSphereError + raise + rescue StandardError => e + raise Errors::VSphereError.new, e.message end + + # TODO: handle interrupted status in the environment, should the vm be destroyed? + @machine.id = new_vm.config.uuid end + end - def destroy - return nil if @machine.id.nil? - return nil unless is_created - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start destroying vm #{@machine.id}") - task = vm.Destroy_Task - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion + def destroy + return nil if @machine.id.nil? + return nil unless is_created + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + task = vm.Destroy_Task + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? end - @logger.info("Finished destroying vm #{@machine.id}") + else + task.wait_for_completion end - - @machine.id = nil + @logger.info("Finished destroying vm #{@machine.id}") end - def is_created - return false if @machine.id.nil? + @machine.id = nil + end - connection do |conn| - vm = get_vm_by_uuid conn, @machine - return false if vm.nil? - end + def is_created + return false if @machine.id.nil? - true + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return false if vm.nil? end - def is_running - state == :running - end + true + end - def snapshot_list - return nil if @machine.id.nil? + def is_running + state == :running + end - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start destroying vm #{@machine.id}") - snapshots = enumerate_snapshots(vm).map(&:name) - @logger.info("Finished destroying vm #{@machine.id}") - return snapshots - end + def snapshot_list + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + snapshots = enumerate_snapshots(vm).map(&:name) + @logger.info("Finished destroying vm #{@machine.id}") + return snapshots end + end - def delete_snapshot(snapshot_name) - return nil if @machine.id.nil? + def delete_snapshot(snapshot_name) + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine + connection do |conn| + vm = get_vm_by_uuid conn, @machine - snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } - # No snapshot matching "name" - return nil if snapshot.nil? + # No snapshot matching "name" + return nil if snapshot.nil? - task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) + task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) - @logger.info("Start deleting snapshot #{snapshot_name} on vm #{@machine.id}") - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion + @logger.info("Start deleting snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? end - @logger.info("Finished deleting snapshot #{snapshot_name} on vm #{@machine.id}") + else + task.wait_for_completion end + @logger.info("Finished deleting snapshot #{snapshot_name} on vm #{@machine.id}") end + end - def restore_snapshot(snapshot_name) - return nil if @machine.id.nil? + def restore_snapshot(snapshot_name) + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine + connection do |conn| + vm = get_vm_by_uuid conn, @machine - snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } - # No snapshot matching "name" - return nil if snapshot.nil? + # No snapshot matching "name" + return nil if snapshot.nil? - task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) + task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) - @logger.info("Start restoring snapshot #{snapshot_name} on vm #{@machine.id}") - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion + @logger.info("Start restoring snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? end - @logger.info("Finished restoring snapshot #{snapshot_name} on vm #{@machine.id}") + else + task.wait_for_completion end + @logger.info("Finished restoring snapshot #{snapshot_name} on vm #{@machine.id}") end + end - def create_snapshot(snapshot_name) - return nil if @machine.id.nil? + def create_snapshot(snapshot_name) + return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine + connection do |conn| + vm = get_vm_by_uuid conn, @machine - task = vm.CreateSnapshot_Task( - name: name, - memory: false, - quiesce: false) + task = vm.CreateSnapshot_Task( + name: name, + memory: false, + quiesce: false) - @logger.info("Start creating snapshot #{snapshot_name} on vm #{@machine.id}") + @logger.info("Start creating snapshot #{snapshot_name} on vm #{@machine.id}") - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? end - - @logger.info("Finished creating snapshot #{snapshot_name} on vm #{@machine.id}") - end - end - - private - - # Enumerate VM snapshot tree - # - # This method returns an enumerator that performs a depth-first walk - # of the VM snapshot grap and yields each VirtualMachineSnapshotTree - # node. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # - # @return [Enumerator] - def enumerate_snapshots(vm) - snapshot_info = vm.snapshot - - if snapshot_info.nil? - snapshot_root = [] else - snapshot_root = snapshot_info.rootSnapshotList + task.wait_for_completion end - recursor = lambda do |snapshot_list| - Enumerator.new do |yielder| - snapshot_list.each do |s| - # Yield the current VirtualMachineSnapshotTree object - yielder.yield s + @logger.info("Finished creating snapshot #{snapshot_name} on vm #{@machine.id}") + end + end - # Recurse into child VirtualMachineSnapshotTree objects - children = recursor.call(s.childSnapshotList) - loop do - yielder.yield children.next - end + private + + # Enumerate VM snapshot tree + # + # This method returns an enumerator that performs a depth-first walk + # of the VM snapshot grap and yields each VirtualMachineSnapshotTree + # node. + # + # @param vm [RbVmomi::VIM::VirtualMachine] + # + # @return [Enumerator] + def enumerate_snapshots(vm) + snapshot_info = vm.snapshot + + if snapshot_info.nil? + snapshot_root = [] + else + snapshot_root = snapshot_info.rootSnapshotList + end + + recursor = lambda do |snapshot_list| + Enumerator.new do |yielder| + snapshot_list.each do |s| + # Yield the current VirtualMachineSnapshotTree object + yielder.yield s + + # Recurse into child VirtualMachineSnapshotTree objects + children = recursor.call(s.childSnapshotList) + loop do + yielder.yield children.next end end end - - recursor.call(snapshot_root) end - def filter_guest_nic(vm, machine) - return vm.guest.ipAddress unless machine.provider_config.real_nic_ip - ip_addresses = vm.guest.net.select { |g| g.deviceConfigId > 0 }.map { |g| g.ipAddress[0] } - fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 - ip_addresses.first - end + recursor.call(snapshot_root) + end - def get_datacenter(connection, machine) - connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) - end + def filter_guest_nic(vm, machine) + return vm.guest.ipAddress unless machine.provider_config.real_nic_ip + ip_addresses = vm.guest.net.select { |g| g.deviceConfigId > 0 }.map { |g| g.ipAddress[0] } + fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 + ip_addresses.first + end - def get_vm_by_uuid(connection, machine) - get_datacenter(connection, machine).vmFolder.findByUuid machine.id - end + def get_datacenter(connection, machine) + connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) + end - def get_resource_pool(datacenter, machine) - rp = get_compute_resource(datacenter, machine) - - resource_pool_name = machine.provider_config.resource_pool_name || '' - - entity_array = resource_pool_name.split('/') - entity_array.each do |entity_array_item| - next if entity_array_item.empty? - if rp.is_a? RbVmomi::VIM::Folder - rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource - rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ResourcePool - rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ComputeResource - rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) - else - fail Errors::VSphereError, :missing_resource_pool - end + def get_vm_by_uuid(connection, machine) + get_datacenter(connection, machine).vmFolder.findByUuid machine.id + end + + def get_resource_pool(datacenter, machine) + rp = get_compute_resource(datacenter, machine) + + resource_pool_name = machine.provider_config.resource_pool_name || '' + + entity_array = resource_pool_name.split('/') + entity_array.each do |entity_array_item| + next if entity_array_item.empty? + if rp.is_a? RbVmomi::VIM::Folder + rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource + rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ResourcePool + rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ComputeResource + rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) + else + fail Errors::VSphereError, :missing_resource_pool end - rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) - rp end + rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) + rp + end - def get_compute_resource(datacenter, machine) - cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) - fail Errors::VSphereError, :missing_compute_resource if cr.nil? - cr + def get_compute_resource(datacenter, machine) + cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) + fail Errors::VSphereError, :missing_compute_resource if cr.nil? + cr + end + + def find_clustercompute_or_compute_resource(datacenter, path) + if path.is_a? String + es = path.split('/').reject(&:empty?) + elsif path.is_a? Enumerable + es = path + else + fail "unexpected path class #{path.class}" end + return datacenter.hostFolder if es.empty? + final = es.pop - def find_clustercompute_or_compute_resource(datacenter, path) - if path.is_a? String - es = path.split('/').reject(&:empty?) - elsif path.is_a? Enumerable - es = path - else - fail "unexpected path class #{path.class}" - end - return datacenter.hostFolder if es.empty? - final = es.pop + p = es.inject(datacenter.hostFolder) do |f, e| + f.find(e, RbVmomi::VIM::Folder) || return + end - p = es.inject(datacenter.hostFolder) do |f, e| - f.find(e, RbVmomi::VIM::Folder) || return + begin + if (x = p.find(final, RbVmomi::VIM::ComputeResource)) + x + elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) + x end - - begin - if (x = p.find(final, RbVmomi::VIM::ComputeResource)) - x - elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) - x - end - rescue Exception - # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead - x = p.childEntity.find { |x2| x2.name == final } - if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) - x - else - puts 'ex unknown type ' + x.to_json - nil - end + rescue Exception + # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead + x = p.childEntity.find { |x2| x2.name == final } + if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) + x + else + puts 'ex unknown type ' + x.to_json + nil end end + end - def get_customization_spec_info_by_name(connection, machine) - name = machine.provider_config.customization_spec_name - return if name.nil? || name.empty? + def get_customization_spec_info_by_name(connection, machine) + name = machine.provider_config.customization_spec_name + return if name.nil? || name.empty? - manager = connection.serviceContent.customizationSpecManager - fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? + manager = connection.serviceContent.customizationSpecManager + fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? - spec = manager.GetCustomizationSpec(name: name) - fail Errors::VSphereError, :missing_configuration_spec if spec.nil? + spec = manager.GetCustomizationSpec(name: name) + fail Errors::VSphereError, :missing_configuration_spec if spec.nil? - spec - end + spec + end - def get_datastore(datacenter, machine) - name = machine.provider_config.data_store_name - return if name.nil? || name.empty? + def get_datastore(datacenter, machine) + name = machine.provider_config.data_store_name + return if name.nil? || name.empty? - # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) - datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) - end + # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) + datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) + end - def get_network_by_name(dc, name) - dc.network.find { |f| f.name == name } || fail(Errors::VSphereError, :missing_vlan) + def get_network_by_name(dc, name) + base = dc.networkFolder + entity_array = name.split('/').reject(&:empty?) + entity_array.each do |item| + case base + when RbVmomi::VIM::Folder + base = base.find(item) + when RbVmomi::VIM::VmwareDistributedVirtualSwitch + idx = base.summary.portgroupName.find_index(item) + base = idx.nil? ? nil : base.portgroup[idx] + end end - #Cloning - def get_customization_spec(machine, spec_info) - customization_spec = spec_info.spec.clone + fail(Errors::VSphereError, :missing_vlan) if base.nil? - # find all the configured private networks - private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } - return customization_spec if private_networks.nil? + base + end - # make sure we have enough NIC settings to override with the private network settings - fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length + #Cloning + def get_customization_spec(machine, spec_info) + customization_spec = spec_info.spec.clone - # assign the private network IP to the NIC - private_networks.each_index do |idx| - customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] - end + # find all the configured private networks + private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } + return customization_spec if private_networks.nil? - customization_spec - end + # make sure we have enough NIC settings to override with the private network settings + fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length - def get_location(datastore, dc, machine, template) - if machine.provider_config.linked_clone - # The API for linked clones is quite strange. We can't create a linked - # straight from any VM. The disks of the VM for which we can create a - # linked clone need to be read-only and thus VC demands that the VM we - # are cloning from uses delta-disks. Only then it will allow us to - # share the base disk. - # - # Thus, this code first create a delta disk on top of the base disk for - # the to-be-cloned VM, if delta disks aren't used already. - disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) - disks.select { |disk| disk.backing.parent.nil? }.each do |disk| - spec = { - deviceChange: [ - { - operation: :remove, - device: disk - }, - { - operation: :add, - fileOperation: :create, - device: disk.dup.tap do |new_disk| - new_disk.backing = new_disk.backing.dup - new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" - new_disk.backing.parent = disk.backing - end - } - ] - } - template.ReconfigVM_Task(spec: spec).wait_for_completion - end + # assign the private network IP to the NIC + private_networks.each_index do |idx| + customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] + end - location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) - elsif datastore.is_a? RbVmomi::VIM::StoragePod - location = RbVmomi::VIM.VirtualMachineRelocateSpec - else - location = RbVmomi::VIM.VirtualMachineRelocateSpec + customization_spec + end - location[:datastore] = datastore unless datastore.nil? + def get_location(datastore, dc, machine, template) + if machine.provider_config.linked_clone + # The API for linked clones is quite strange. We can't create a linked + # straight from any VM. The disks of the VM for which we can create a + # linked clone need to be read-only and thus VC demands that the VM we + # are cloning from uses delta-disks. Only then it will allow us to + # share the base disk. + # + # Thus, this code first create a delta disk on top of the base disk for + # the to-be-cloned VM, if delta disks aren't used already. + disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) + disks.select { |disk| disk.backing.parent.nil? }.each do |disk| + spec = { + deviceChange: [ + { + operation: :remove, + device: disk + }, + { + operation: :add, + fileOperation: :create, + device: disk.dup.tap do |new_disk| + new_disk.backing = new_disk.backing.dup + new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" + new_disk.backing.parent = disk.backing + end + } + ] + } + template.ReconfigVM_Task(spec: spec).wait_for_completion end - location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm - location + + location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) + elsif datastore.is_a? RbVmomi::VIM::StoragePod + location = RbVmomi::VIM.VirtualMachineRelocateSpec + else + location = RbVmomi::VIM.VirtualMachineRelocateSpec + + location[:datastore] = datastore unless datastore.nil? end + location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm + location + end - def get_name(machine, config, root_path) - return config.name unless config.name.nil? + def get_name(machine, config, root_path) + return config.name unless config.name.nil? - prefix = "#{root_path.basename}_#{machine.name}" - prefix.gsub!(/[^-a-z0-9_\.]/i, '') - # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs - prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" + prefix = "#{root_path.basename}_#{machine.name}" + prefix.gsub!(/[^-a-z0-9_\.]/i, '') + # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs + prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" + end + + def get_vm_base_folder(dc, template, config) + if config.vm_base_path.nil? + template.parent + else + dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) end + end - def get_vm_base_folder(dc, template, config) - if config.vm_base_path.nil? - template.parent - else - dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) + def configure_network_cards(spec, config, template) + #Enumerate lan cards + current_adapters = Hash.new + + template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| + current_adapters[index] = item + } + + #remove unused network interfaces + if config.destroy_unused_network_interfaces + for index in (current_adapters.length-1).downto(config.network_adapters.length-1) + adapter = current_adapters[index] + + remove_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), + :device => adapter + } + + spec[:config][:deviceChange].push remove_adaptor + spec[:config][:deviceChange].uniq! end end - def modify_network_card(template, spec) - spec[:config][:deviceChange] ||= [] - @card ||= template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first + #add extra network interfaces + for index in (current_adapters.length-1).to(config.network_adapters.length-1) + adapter_configuration = config.network_adapters[index] + adapter = RbVmomi::VIM::VirtualVmxnet3( + :key => index, + :deviceInfo => RbVmomi::VIM::Description(), + :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() + ) + + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - fail Errors::VSphereError, :missing_network_card if @card.nil? + adapter = configure_network_card(adapter_configuration, adapter, label, summary) - yield(@card) + add_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), + :device => adapter + } - dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(device: @card, operation: 'edit') - spec[:config][:deviceChange].push dev_spec + spec[:config][:deviceChange].push add_adaptor spec[:config][:deviceChange].uniq! end - def add_custom_address_type(template, spec, addressType) - spec[:config][:deviceChange] = [] - config = template.config - card = config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first || fail(Errors::VSphereError, :missing_network_card) - card.addressType = addressType - card_spec = { :deviceChange => [{ :operation => :edit, :device => card }] } - template.ReconfigVM_Task(:spec => card_spec).wait_for_completion + #we have 5 cards but want 8 cards + #add 3 cards + #edit first 5 cards + number_of_existing_adapters = current_adapters.length-1 + if current_adapters.length-1 > config.network_adapters.length-1 + #we have 5 cards but want 3 cards + #remove 2 cards + #edit first 3 cards + number_of_existing_adapters = config.network_adapters.length-1 end - def add_custom_mac(template, spec, mac) - modify_network_card(template, spec) do |card| - card.macAddress = mac - end - end + #edit existing network interfaces + for index in (0).to(number_of_existing_adapters) + adapter_configuration = config.network_adapters[index] + adapter = current_adapters[index] - def add_custom_vlan(template, dc, spec, vlan) - network = get_network_by_name(dc, vlan) + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - modify_network_card(template, spec) do |card| - begin - switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) - card.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) - rescue - # not connected to a distibuted switch? - card.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) - end - end - end + adapter = configure_network_card(adapter_configuration, adapter, label, summary) - def add_custom_memory(spec, memory_mb) - spec[:config][:memoryMB] = Integer(memory_mb) - end + edit_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => adapter + } - def add_custom_cpu(spec, cpu_count) - spec[:config][:numCPUs] = Integer(cpu_count) + spec[:config][:deviceChange].push edit_adaptor + spec[:config][:deviceChange].uniq! end - def add_custom_cpu_reservation(spec, cpu_reservation) - spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) - end + spec + end - def add_custom_mem_reservation(spec, mem_reservation) - spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) - end + def configure_network_card(adapter_configuration, adapter, label, summary) + adapter_configuration = config.network_adapters[index] + adapter = current_adapters[index] - def add_custom_extra_config(spec, extra_config = {}) - return if extra_config.empty? + if !adapter_configuration.vlan.nil? + network = get_network_by_name(dc, adapter_configuration.vlan) - # extraConfig must be an array of hashes with `key` and `value` - # entries. - spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } + if network.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) + switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) + adapter.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) + else + # not connected to a distibuted switch? + adapter.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) + end end - def add_custom_notes(spec, notes) - spec[:config][:annotation] = notes - end - end + adapter.deviceInfo.label = label unless label.nil? + adapter.deviceInfo.summary = summary unless summary.nil? + + adapter.connectable.allowGuestControl = adapter_configuration.startConnected unless adapter_configuration.allowGuestControl.nil? + adapter.connectable.connected = adapter_configuration.startConnected unless adapter_configuration.connected.nil? + adapter.connectable.startConnected = adapter_configuration.startConnected unless adapter_configuration.startConnected.nil? + + adapter.addressType = adapter_configuration.addressType unless adapter_configuration.addressType.nil? + adapter.macAddress = adapter_configuration.addressType unless adapter_configuration.macAddress.nil? + adapter.wakeOnLanEnabled = adapter_configuration.addressType unless adapter_configuration.wakeOnLanEnabled.nil? + + adapter + end + + def add_custom_memory(spec, memory_mb) + spec[:config][:memoryMB] = Integer(memory_mb) + end + + def add_custom_cpu(spec, cpu_count) + spec[:config][:numCPUs] = Integer(cpu_count) + end + + def add_custom_cpu_reservation(spec, cpu_reservation) + spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) + end + + def add_custom_mem_reservation(spec, mem_reservation) + spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) + end + + def add_custom_extra_config(spec, extra_config = {}) + return if extra_config.empty? + + # extraConfig must be an array of hashes with `key` and `value` + # entries. + spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } + end + + def add_custom_notes(spec, notes) + spec[:config][:annotation] = notes + end + end end end \ No newline at end of file From e86ece044d217f7c732778dbc3d3a44cb430d126 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 13:55:02 +0000 Subject: [PATCH 03/39] fix typo. Should have been upto instead of to --- lib/vSphere/driver.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 55604e29..f1a8292a 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -607,7 +607,7 @@ def configure_network_cards(spec, config, template) end #add extra network interfaces - for index in (current_adapters.length-1).to(config.network_adapters.length-1) + for index in (current_adapters.length-1).upto(config.network_adapters.length-1) adapter_configuration = config.network_adapters[index] adapter = RbVmomi::VIM::VirtualVmxnet3( :key => index, @@ -642,7 +642,7 @@ def configure_network_cards(spec, config, template) end #edit existing network interfaces - for index in (0).to(number_of_existing_adapters) + for index in (0).upto(number_of_existing_adapters) adapter_configuration = config.network_adapters[index] adapter = current_adapters[index] From c745f98c3f7c428759f9c8f2703c31cbaf8d778b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 14:39:07 +0000 Subject: [PATCH 04/39] Parse hash configuration into a strong type for configuration --- lib/vSphere/config.rb | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 3ab84287..22b9c7b5 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -3,6 +3,28 @@ module VagrantPlugins module VSphere class Config < Vagrant.plugin('2', :config) + + class NetworkConfiguration + attr_accessor :allowGuestControl + attr_accessor :connected + attr_accessor :startConnected + + attr_accessor :vlan + attr_accessor :addressType + attr_accessor :macAddress + attr_accessor :wakeOnLanEnabled + + def initialize(config) + @allowGuestControl = config[:allowGuestControl] if config.key?(:allowGuestControl) + @connected = config[:connected] if config.key?(:connected) + @startConnected = config[:startConnected] if config.key?(:startConnected) + @vlan = config[:vlan] if config.key?(:vlan) + @addressType = config[:addressType] if config.key?(:addressType) + @macAddress = config[:macAddress] if config.key?(:macAddress) + @wakeOnLanEnabled = config[:wakeOnLanEnabled] if config.key?(:wakeOnLanEnabled) + end + end + attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] attr_accessor :host attr_accessor :insecure @@ -45,16 +67,9 @@ def custom_attribute(key, value) @custom_attributes[key.to_sym] = value end - #attr_accessor :allowGuestControl - #attr_accessor :connected - #attr_accessor :startConnected - #attr_accessor :vlan - #attr_accessor :addressType - #attr_accessor :macAddress - #attr_accessor :wakeOnLanEnabled def network_adapter(slot, **opts) - @network_adapters[slot] = opts + @network_adapters[slot] = NetworkConfiguration.new opts end def finalize! From fb990e4e19a2e842f77234ae4beacff049bbb259 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 14:45:51 +0000 Subject: [PATCH 05/39] Use a parameter name that is not duplicated --- lib/vSphere/config.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 22b9c7b5..11a9247c 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -14,14 +14,14 @@ class NetworkConfiguration attr_accessor :macAddress attr_accessor :wakeOnLanEnabled - def initialize(config) - @allowGuestControl = config[:allowGuestControl] if config.key?(:allowGuestControl) - @connected = config[:connected] if config.key?(:connected) - @startConnected = config[:startConnected] if config.key?(:startConnected) - @vlan = config[:vlan] if config.key?(:vlan) - @addressType = config[:addressType] if config.key?(:addressType) - @macAddress = config[:macAddress] if config.key?(:macAddress) - @wakeOnLanEnabled = config[:wakeOnLanEnabled] if config.key?(:wakeOnLanEnabled) + def initialize(network_config) + @allowGuestControl = network_config[:allowGuestControl] if network_config.key?(:allowGuestControl) + @connected = network_config[:connected] if network_config.key?(:connected) + @startConnected = network_config[:startConnected] if network_config.key?(:startConnected) + @vlan = network_config[:vlan] if network_config.key?(:vlan) + @addressType = network_config[:addressType] if network_config.key?(:addressType) + @macAddress = network_config[:macAddress] if network_config.key?(:macAddress) + @wakeOnLanEnabled = network_config[:wakeOnLanEnabled] if network_config.key?(:wakeOnLanEnabled) end end From ad42c429f88617b42947294b948d05e6a9a14f90 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 14:51:27 +0000 Subject: [PATCH 06/39] Remove accidentally included code --- lib/vSphere/driver.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index f1a8292a..c49fff42 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -665,9 +665,6 @@ def configure_network_cards(spec, config, template) end def configure_network_card(adapter_configuration, adapter, label, summary) - adapter_configuration = config.network_adapters[index] - adapter = current_adapters[index] - if !adapter_configuration.vlan.nil? network = get_network_by_name(dc, adapter_configuration.vlan) From 7b7fe1e158f666aa385d576e865fa5b4dcd57ac1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 14:57:38 +0000 Subject: [PATCH 07/39] need to pass dc in --- lib/vSphere/driver.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index c49fff42..73724b4e 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -147,7 +147,7 @@ def clone(root_path) customization_info = get_customization_spec_info_by_name conn, @machine spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? - spec = configure_network_cards(spec, config, template) + spec = configure_network_cards(spec, dc, template, config) add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? @@ -583,7 +583,7 @@ def get_vm_base_folder(dc, template, config) end end - def configure_network_cards(spec, config, template) + def configure_network_cards(spec, dc, template, config) #Enumerate lan cards current_adapters = Hash.new @@ -619,7 +619,7 @@ def configure_network_cards(spec, config, template) summary = nil summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - adapter = configure_network_card(adapter_configuration, adapter, label, summary) + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) add_adaptor = { :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), @@ -650,7 +650,7 @@ def configure_network_cards(spec, config, template) summary = nil summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - adapter = configure_network_card(adapter_configuration, adapter, label, summary) + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) edit_adaptor = { :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), @@ -664,7 +664,7 @@ def configure_network_cards(spec, config, template) spec end - def configure_network_card(adapter_configuration, adapter, label, summary) + def configure_network_card(dc, adapter_configuration, adapter, label, summary) if !adapter_configuration.vlan.nil? network = get_network_by_name(dc, adapter_configuration.vlan) From cdd9b2b673fbd7684f96ce8fe25f7a47dfe855c4 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 15:05:29 +0000 Subject: [PATCH 08/39] Ensure that its an empty array --- lib/vSphere/driver.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 73724b4e..d843a067 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -585,6 +585,8 @@ def get_vm_base_folder(dc, template, config) def configure_network_cards(spec, dc, template, config) #Enumerate lan cards + spec[:config][:deviceChange] ||= [] + current_adapters = Hash.new template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| From 6f337039609f160190e6a3316804e447a846d624 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 15:17:36 +0000 Subject: [PATCH 09/39] Set default values for network configuration as values are not optional --- lib/vSphere/config.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 11a9247c..73408094 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -15,6 +15,15 @@ class NetworkConfiguration attr_accessor :wakeOnLanEnabled def initialize(network_config) + @allowGuestControl = false + @connected = true + @startConnected = true + + @vlan = nil + @addressType = 'generated' + @macAddress = nil + @wakeOnLanEnabled = false + @allowGuestControl = network_config[:allowGuestControl] if network_config.key?(:allowGuestControl) @connected = network_config[:connected] if network_config.key?(:connected) @startConnected = network_config[:startConnected] if network_config.key?(:startConnected) From 80f14cdd82124766c8a2a9520fd245710c5cb48e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 15:25:28 +0000 Subject: [PATCH 10/39] Match configuration property with adapter property --- lib/vSphere/driver.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index d843a067..5a39c37b 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -682,13 +682,13 @@ def configure_network_card(dc, adapter_configuration, adapter, label, summary) adapter.deviceInfo.label = label unless label.nil? adapter.deviceInfo.summary = summary unless summary.nil? - adapter.connectable.allowGuestControl = adapter_configuration.startConnected unless adapter_configuration.allowGuestControl.nil? - adapter.connectable.connected = adapter_configuration.startConnected unless adapter_configuration.connected.nil? + adapter.connectable.allowGuestControl = adapter_configuration.allowGuestControl unless adapter_configuration.allowGuestControl.nil? + adapter.connectable.connected = adapter_configuration.connected unless adapter_configuration.connected.nil? adapter.connectable.startConnected = adapter_configuration.startConnected unless adapter_configuration.startConnected.nil? adapter.addressType = adapter_configuration.addressType unless adapter_configuration.addressType.nil? - adapter.macAddress = adapter_configuration.addressType unless adapter_configuration.macAddress.nil? - adapter.wakeOnLanEnabled = adapter_configuration.addressType unless adapter_configuration.wakeOnLanEnabled.nil? + adapter.macAddress = adapter_configuration.macAddress unless adapter_configuration.macAddress.nil? + adapter.wakeOnLanEnabled = adapter_configuration.wakeOnLanEnabled unless adapter_configuration.wakeOnLanEnabled.nil? adapter end From 84ad27b8220246fbebb3e2f53757849585cc9939 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 15:53:16 +0000 Subject: [PATCH 11/39] Configure existing cards before adding new ones Show debug output for network card configuration --- lib/vSphere/driver.rb | 52 ++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 5a39c37b..6fcc9d8b 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -586,7 +586,7 @@ def get_vm_base_folder(dc, template, config) def configure_network_cards(spec, dc, template, config) #Enumerate lan cards spec[:config][:deviceChange] ||= [] - + current_adapters = Hash.new template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| @@ -608,30 +608,6 @@ def configure_network_cards(spec, dc, template, config) end end - #add extra network interfaces - for index in (current_adapters.length-1).upto(config.network_adapters.length-1) - adapter_configuration = config.network_adapters[index] - adapter = RbVmomi::VIM::VirtualVmxnet3( - :key => index, - :deviceInfo => RbVmomi::VIM::Description(), - :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() - ) - - label = "Ethernet #{index+1}" - summary = nil - summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - - adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) - - add_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), - :device => adapter - } - - spec[:config][:deviceChange].push add_adaptor - spec[:config][:deviceChange].uniq! - end - #we have 5 cards but want 8 cards #add 3 cards #edit first 5 cards @@ -663,6 +639,32 @@ def configure_network_cards(spec, dc, template, config) spec[:config][:deviceChange].uniq! end + #add extra network interfaces + for index in (current_adapters.length-1).upto(config.network_adapters.length-1) + adapter_configuration = config.network_adapters[index] + adapter = RbVmomi::VIM::VirtualVmxnet3( + :key => index, + :deviceInfo => RbVmomi::VIM::Description(), + :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() + ) + + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? + + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) + + add_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), + :device => adapter + } + + spec[:config][:deviceChange].push add_adaptor + spec[:config][:deviceChange].uniq! + end + + puts "spec[:config] = #{spec[:config].inspect}" + spec end From 53827fecca07ea78cd00953ed60983e306620b2c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 16:28:16 +0000 Subject: [PATCH 12/39] Fix the adding, configuring and removing of network adaptors --- lib/vSphere/driver.rb | 54 +++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 6fcc9d8b..f607488d 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -595,52 +595,56 @@ def configure_network_cards(spec, dc, template, config) #remove unused network interfaces if config.destroy_unused_network_interfaces - for index in (current_adapters.length-1).downto(config.network_adapters.length-1) - adapter = current_adapters[index] + if current_adapters.length-1 > config.network_adapters.length-1 + for index in (current_adapters.length-1).downto(config.network_adapters.length-1+1) + adapter = current_adapters[index] - remove_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), - :device => adapter - } + remove_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), + :device => adapter + } - spec[:config][:deviceChange].push remove_adaptor - spec[:config][:deviceChange].uniq! + spec[:config][:deviceChange].push remove_adaptor + spec[:config][:deviceChange].uniq! + end end end #we have 5 cards but want 8 cards #add 3 cards #edit first 5 cards - number_of_existing_adapters = current_adapters.length-1 - if current_adapters.length-1 > config.network_adapters.length-1 + number_of_existing_adapters = current_adapters.length + if current_adapters.length > config.network_adapters.length #we have 5 cards but want 3 cards #remove 2 cards #edit first 3 cards - number_of_existing_adapters = config.network_adapters.length-1 + number_of_existing_adapters = config.network_adapters.length end #edit existing network interfaces - for index in (0).upto(number_of_existing_adapters) - adapter_configuration = config.network_adapters[index] - adapter = current_adapters[index] + if (number_of_existing_adapters > 0) + for index in (0).upto(number_of_existing_adapters) + adapter_configuration = config.network_adapters[index] + adapter = current_adapters[index] - label = "Ethernet #{index+1}" - summary = nil - summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) - edit_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), - :device => adapter - } + edit_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => adapter + } - spec[:config][:deviceChange].push edit_adaptor - spec[:config][:deviceChange].uniq! + spec[:config][:deviceChange].push edit_adaptor + spec[:config][:deviceChange].uniq! + end end #add extra network interfaces - for index in (current_adapters.length-1).upto(config.network_adapters.length-1) + for index in (number_of_existing_adapter).upto(config.network_adapters.length-1) adapter_configuration = config.network_adapters[index] adapter = RbVmomi::VIM::VirtualVmxnet3( :key => index, From 4cb3c3c2e8063bc1462033d140e25f138de4b519 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 8 Nov 2016 16:34:11 +0000 Subject: [PATCH 13/39] Make it obvious where its going wrong --- lib/vSphere/driver.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index f607488d..46ad280a 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -216,8 +216,8 @@ def clone(root_path) end rescue Errors::VSphereError raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message + #rescue StandardError => e + # raise Errors::VSphereError.new, e.message end # TODO: handle interrupted status in the environment, should the vm be destroyed? From f7abb68b8560b4a82a639a31451256e08b8b233c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 9 Nov 2016 15:42:33 +0000 Subject: [PATCH 14/39] Show network debug configuration --- lib/vSphere/driver.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 46ad280a..9f7c9aff 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -593,6 +593,8 @@ def configure_network_cards(spec, dc, template, config) current_adapters[index] = item } + puts "config.network_adapters=#{config.network_adapters.inspect}" + #remove unused network interfaces if config.destroy_unused_network_interfaces if current_adapters.length-1 > config.network_adapters.length-1 @@ -625,6 +627,9 @@ def configure_network_cards(spec, dc, template, config) if (number_of_existing_adapters > 0) for index in (0).upto(number_of_existing_adapters) adapter_configuration = config.network_adapters[index] + + puts "adapter_configuration=#{adapter_configuration.inspect}" + adapter = current_adapters[index] label = "Ethernet #{index+1}" From ab2182d9173599445429b7687404624f633cf069 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 9 Nov 2016 17:01:39 +0000 Subject: [PATCH 15/39] There can be gaps in the network card configuration, so when this happens treat it as if there are no changes required for network card. --- lib/vSphere/driver.rb | 52 ++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 9f7c9aff..169665e6 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -595,10 +595,24 @@ def configure_network_cards(spec, dc, template, config) puts "config.network_adapters=#{config.network_adapters.inspect}" + current_adapters_length = current_adapters.length + + # configuration may have gaps in it, so configuration may look like this: + # vsphere.network_adapter 0, vlan: "vlan0" + # vsphere.network_adapter 1, vlan: "vlan1" + # vsphere.network_adapter 9, vlan: "vlan9" + config_network_adapters_length = -1 + config.network_adapters.each_with_index { |item, index| + if index > config_network_adapters_length + config_network_adapters_length = index + end + } + config_network_adapters_length += 1 + #remove unused network interfaces if config.destroy_unused_network_interfaces - if current_adapters.length-1 > config.network_adapters.length-1 - for index in (current_adapters.length-1).downto(config.network_adapters.length-1+1) + if current_adapters_length-1 > network_adapters_length-1 + for index in (current_adapters_length-1).downto(network_adapters_length-1+1) adapter = current_adapters[index] remove_adaptor = { @@ -615,36 +629,38 @@ def configure_network_cards(spec, dc, template, config) #we have 5 cards but want 8 cards #add 3 cards #edit first 5 cards - number_of_existing_adapters = current_adapters.length - if current_adapters.length > config.network_adapters.length + number_of_existing_adapters = current_adapters_length + if current_adapters_length > network_adapters_length #we have 5 cards but want 3 cards #remove 2 cards #edit first 3 cards - number_of_existing_adapters = config.network_adapters.length + number_of_existing_adapters = network_adapters_length end #edit existing network interfaces if (number_of_existing_adapters > 0) for index in (0).upto(number_of_existing_adapters) adapter_configuration = config.network_adapters[index] + puts "adapter_configuration[#{index}]=#{adapter_configuration.inspect}" - puts "adapter_configuration=#{adapter_configuration.inspect}" - - adapter = current_adapters[index] + #there may be no configuration for this card so dont change it, if this is the case + if !adapter_configuration.nil? + adapter = current_adapters[index] - label = "Ethernet #{index+1}" - summary = nil - summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) - edit_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), - :device => adapter - } + edit_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => adapter + } - spec[:config][:deviceChange].push edit_adaptor - spec[:config][:deviceChange].uniq! + spec[:config][:deviceChange].push edit_adaptor + spec[:config][:deviceChange].uniq! + end end end From 64b2cac7a06369a2f78a48aeb67f00e9cf052ecb Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 9 Nov 2016 17:07:27 +0000 Subject: [PATCH 16/39] Missing prefix on variable name --- lib/vSphere/driver.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 169665e6..9f3f86d9 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -611,8 +611,8 @@ def configure_network_cards(spec, dc, template, config) #remove unused network interfaces if config.destroy_unused_network_interfaces - if current_adapters_length-1 > network_adapters_length-1 - for index in (current_adapters_length-1).downto(network_adapters_length-1+1) + if current_adapters_length-1 > config_network_adapters_length-1 + for index in (current_adapters_length-1).downto(config_network_adapters_length-1+1) adapter = current_adapters[index] remove_adaptor = { @@ -630,11 +630,11 @@ def configure_network_cards(spec, dc, template, config) #add 3 cards #edit first 5 cards number_of_existing_adapters = current_adapters_length - if current_adapters_length > network_adapters_length + if current_adapters_length > config_network_adapters_length #we have 5 cards but want 3 cards #remove 2 cards #edit first 3 cards - number_of_existing_adapters = network_adapters_length + number_of_existing_adapters = config_network_adapters_length end #edit existing network interfaces From a1f78fe2efdf56d0eaa7b51e225ab288677ba7d3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 9 Nov 2016 17:11:36 +0000 Subject: [PATCH 17/39] Missing s on variable name --- lib/vSphere/driver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 9f3f86d9..83c10fa3 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -665,7 +665,7 @@ def configure_network_cards(spec, dc, template, config) end #add extra network interfaces - for index in (number_of_existing_adapter).upto(config.network_adapters.length-1) + for index in (number_of_existing_adapters).upto(config_network_adapters_length-1) adapter_configuration = config.network_adapters[index] adapter = RbVmomi::VIM::VirtualVmxnet3( :key => index, From 0fdc1a9be8fcd1b68c8aeb35f99eaac9ef3ff903 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 15 Nov 2016 13:24:31 +0000 Subject: [PATCH 18/39] Can rely on Communicator polling to wait for ip address. --- lib/vSphere/action.rb | 2 - lib/vSphere/action/wait_for_ip_address.rb | 65 ----------------------- lib/vSphere/config.rb | 3 -- lib/vSphere/driver.rb | 1 + 4 files changed, 1 insertion(+), 70 deletions(-) delete mode 100644 lib/vSphere/action/wait_for_ip_address.rb diff --git a/lib/vSphere/action.rb b/lib/vSphere/action.rb index da7b266e..2c8367f6 100644 --- a/lib/vSphere/action.rb +++ b/lib/vSphere/action.rb @@ -107,7 +107,6 @@ def self.action_up b.use Call, IsRunning do |env, b2| b2.use PowerOn unless env[:result] end - b.use WaitForIPAddress b.use WaitForCommunicator, [:running] b.use Provision b.use SyncedFolders @@ -214,7 +213,6 @@ def self.action_snapshot_save autoload :MessageNotRunning, action_root.join('message_not_running') autoload :PowerOff, action_root.join('power_off') autoload :PowerOn, action_root.join('power_on') - autoload :WaitForIPAddress, action_root.join('wait_for_ip_address') # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. # rubocop:disable IndentationWidth diff --git a/lib/vSphere/action/wait_for_ip_address.rb b/lib/vSphere/action/wait_for_ip_address.rb deleted file mode 100644 index e90a8688..00000000 --- a/lib/vSphere/action/wait_for_ip_address.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'ipaddr' -require 'timeout' - -module VagrantPlugins - module VSphere - module Action - class WaitForIPAddress - def initialize(app, _env) - @app = app - end - - def call(env) - machine = env[:machine] - driver = machine.provider.driver - timeout = machine.provider_config.ip_address_timeout - - env[:ui].output('Waiting for the machine to report its IP address...') - env[:ui].detail("Timeout: #{timeout} seconds") - - guest_ip = nil - - fail Errors::VSphereError, :wait_for_ip_address_timeout unless driver.is_created - - Timeout.timeout(timeout) do - loop do - # If a ctrl-c came through, break out - return if env[:interrupted] - - if driver.is_running - ssh_info = driver.ssh_info - - if ssh_info.nil? - env[:ui].info("Waiting for ip address") - else - guest_ip = ssh_info[:host] - - begin - IPAddr.new(guest_ip) - break - rescue IPAddr::InvalidAddressError - # Ignore, continue looking. - env[:ui].warn("Invalid IP address returned: #{guest_ip}") - end - end - else - env[:ui].warn("Machine is not running") - end - - sleep 1 - end - end - - # If we were interrupted then return now - return if env[:interrupted] - - env[:ui].detail("IP: #{guest_ip}") - - @app.call(env) - rescue Timeout::Error - fail Errors::VSphereError, :wait_for_ip_address_timeout - end - end - end - end -end diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 73408094..8e995842 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -34,7 +34,6 @@ def initialize(network_config) end end - attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] attr_accessor :host attr_accessor :insecure attr_accessor :user @@ -65,7 +64,6 @@ def initialize(network_config) attr_reader :custom_attributes def initialize - @ip_address_timeout = UNSET_VALUE @destroy_unused_network_interfaces = UNSET_VALUE @network_adapters = {} @custom_attributes = {} @@ -82,7 +80,6 @@ def network_adapter(slot, **opts) end def finalize! - @ip_address_timeout = 240 if @ip_address_timeout == UNSET_VALUE end def validate(machine) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 83c10fa3..2ce73747 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -43,6 +43,7 @@ def ssh_info connection do |conn| vm = get_vm_by_uuid conn, @machine return nil if vm.nil? + return nil unless vm.runtime.powerState.eql?(VmState::POWERED_ON) ip_address = filter_guest_nic(vm, @machine) return nil if ip_address.nil? || ip_address.empty? From b1c072e15e33322e8d29599bb9f259dc8b9ec6a8 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 15 Nov 2016 19:59:40 +0000 Subject: [PATCH 19/39] Add serial port configuration --- README.md | 44 ++++++++++++++- lib/vSphere/config.rb | 73 ++++++++++++++++++++++++- lib/vSphere/driver.rb | 123 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8f656669..02ee9271 100644 --- a/README.md +++ b/README.md @@ -148,8 +148,9 @@ This provider has the following settings, all are required unless noted: * `destroy_unused_network_interfaces` - _Optional_ boolean - should network cards that have not been configured explicitly, be deleted. If set to false then existing network cards are left alone. * `network_adapter` - Array of network card configuration +* `serial_port` - Array of serial port configuration -Network card configuration: +## Network card configuration * `slot` - integer - zero based array of the card index * `allowGuestControl` - _Optional_ boolean - Configure the address type of the * `connected` - _Optional_ boolean - is the vm network card connected to the network @@ -160,6 +161,47 @@ Network card configuration: * `macAddress` - _Optional_ string - Used to set the mac address of the new VM * `wakeOnLanEnabled` - _Optional_ boolean - should vm turn on when magic packet is received on network card +## Serial port configuration +* `yieldOnPoll` - _Optional_ boolean - Enables CPU yield behavior. If you set yieldOnPoll to true, the virtual machine will + periodically relinquish the processor if its sole task is polling the virtual serial port. The amount of time it takes to + regain the processor will depend on the degree of other virtual machine activity on the host. +* `connected` - _Optional_ boolean - is the vm serial port connected +* `startConnected` - _Optional_ boolean - When VM is turned on should the vm serial port be connected +* `backing` - _Optional_ string - The type of serial port backing to use. Possible values are 'uri', 'pipe', 'file', 'device'. + + `uri` supports a connection between the virtual machine and a resource on the network. The virtual machine can initiate a connection + with the network resource, or it can listen for connections originating from the network. + + `pipe` supports I/O through a named pipe. The pipe connects the virtual machine to a host application or a virtual machine on the same host. + + `file` supports output through the virtual serial port to a file on the same host. + + `device` supports a connection between the virtual machine and a device that is connected to a physical serial port on the host. + +### uri +* `direction` - _Optional_ string - The direction of the connection. Possible values are 'client' and 'server' +* `proxyURI` - _Optional_ string - Identifies a proxy service that provides network access to the serviceURI. If you specify + a proxy URI, the virtual machine initiates a connection with the proxy service and forwards the serviceURI and direction to the proxy. +* `serviceURI` - _Optional_ string - Identifies the local host or a system on the network, depending on the value of + direction. If you use the virtual machine as a server, the URI identifies the host on which the virtual machine + runs. In this case, the host name part of the URI should be empty, or it should specify the address of the local host. + If you use the virtual machine as a client, the URI identifies the remote system on the network. + +### pipe +* `endpoint` - _Optional_ string - Indicates the role the virtual machine assumes as an endpoint for the pipe. + Possible values are 'client' and 'server' +* `noRxLoss` - _Optional_ boolean - Enables optimized data transfer over the pipe. When you use this feature, + the ESX server buffers data to prevent data overrun. This allows the virtual machine to read all of the data + transferred over the pipe with no data loss. To use optimized data transfer, set noRxLoss to true. To disable + this feature, set the property to false. + +### file +* `fileName` - _Optional_ string - Filename for the host file used in this backing. + +### device +* `deviceName` - _Optional_ string - The name of the device on the host system. +* `useAutoDetect` - _Optional_ boolean - Indicates whether the device should be auto detected instead of directly specified. If this value is set to TRUE, deviceName is ignored. + ### Cloning from a VM rather than a template To clone from an existing VM rather than a template, set `clone_from_vm` to diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 8e995842..77ae1c62 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -34,6 +34,70 @@ def initialize(network_config) end end + class SerialPortConfiguration + attr_accessor :yieldOnPoll + attr_accessor :connected + attr_accessor :startConnected + attr_accessor :backing + + attr_accessor :direction + attr_accessor :proxyURI + attr_accessor :serviceURI + + attr_accessor :endpoint + attr_accessor :noRxLoss + + attr_accessor :fileName + + attr_accessor :deviceName + attr_accessor :useAutoDetect + + def initialize(serial_port_config) + @yieldOnPoll = true + @connected = true + @startConnected = true + @backing = '' + + @direction = '' + @proxyURI = '' + @serviceURI = '' + + @endpoint = '' + @noRxLoss = true + + @fileName = '' + + @deviceName = '' + @useAutoDetect = false + + @yieldOnPoll = serial_port_config[:yieldOnPoll] if serial_port_config.key?(:yieldOnPoll) + @connected = network_config[:connected] if network_config.key?(:connected) + @startConnected = network_config[:startConnected] if network_config.key?(:startConnected) + @backing = serial_port_config[:backing] if serial_port_config.key?(:backing) + if !(@backing == 'uri' || @backing == 'pipe' || @backing == 'file' || @backing == 'device') + raise "The only valid values allowed for backing are 'uri', 'pipe', 'file', 'device'" + end + + @direction = serial_port_config[:direction] if serial_port_config.key?(:direction) + if @backing == 'uri' && !(@direction == 'client' || @direction == 'server') + raise "The only valid values allowed for direction are 'client', 'server'" + end + @proxyURI = serial_port_config[:proxyURI] if serial_port_config.key?(:proxyURI) + @serviceURI = serial_port_config[:serviceURI] if serial_port_config.key?(:serviceURI) + + @endpoint = serial_port_config[:endpoint] if serial_port_config.key?(:endpoint) + if @backing == 'pipe' && !(@endpoint == 'client' || @endpoint == 'server') + raise "The only valid values allowed for endpoint are 'client', 'server'" + end + @noRxLoss = serial_port_config[:noRxLoss] if serial_port_config.key?(:noRxLoss) + + @fileName = serial_port_config[:fileName] if serial_port_config.key?(:fileName) + + @deviceName = serial_port_config[:deviceName] if serial_port_config.key?(:deviceName) + @useAutoDetect = serial_port_config[:useAutoDetect] if serial_port_config.key?(:useAutoDetect) + end + end + attr_accessor :host attr_accessor :insecure attr_accessor :user @@ -60,13 +124,17 @@ def initialize(network_config) attr_accessor :real_nic_ip attr_accessor :destroy_unused_network_interfaces + attr_accessor :destroy_unused_serial_ports attr_reader :network_adapters + attr_reader :serial_ports attr_reader :custom_attributes def initialize @destroy_unused_network_interfaces = UNSET_VALUE + @destroy_unused_serial_ports = UNSET_VALUE @network_adapters = {} @custom_attributes = {} + @serial_ports = {} @extra_config = {} end @@ -74,11 +142,14 @@ def custom_attribute(key, value) @custom_attributes[key.to_sym] = value end - def network_adapter(slot, **opts) @network_adapters[slot] = NetworkConfiguration.new opts end + def serial_port(slot, **opts) + @serial_ports[slot] = SerialPortConfiguration.new opts + end + def finalize! end diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 2ce73747..8dc82dc5 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -584,6 +584,129 @@ def get_vm_base_folder(dc, template, config) end end + def configure_serial_ports(spec, dc, template, config) + #Enumerate serial ports + spec[:config][:deviceChange] ||= [] + + current_ports = Hash.new + + template.config.hardware.device.grep(RbVmomi::VIM::VirtualSerialPort).each_with_index { |item, index| + current_ports[index] = item + } + + puts "config.serial_ports=#{config.serial_ports.inspect}" + + current_ports_length = current_ports.length + + config_serial_ports_length = -1 + config.serial_ports.each_with_index { |item, index| + if index > config_serial_ports_length + config_serial_ports_length = index + end + } + config_serial_ports_length += 1 + + #remove unused serial ports + if config.destroy_unused_serial_ports + if current_ports_length-1 > config_serial_ports_length-1 + for index in (current_ports_length-1).downto(config_serial_ports_length-1+1) + port = current_ports[index] + + remove_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), + :device => port + } + + spec[:config][:deviceChange].push remove_port + spec[:config][:deviceChange].uniq! + end + end + end + + #we have 5 ports but want 8 ports + #add 3 ports + #edit first 5 ports + number_of_existing_ports = current_ports_length + if current_ports_length > config_serial_ports_length + #we have 5 ports but want 3 ports + #remove 2 ports + #edit first 3 ports + number_of_existing_ports = config_serial_ports_length + end + + #edit existing network interfaces + if (number_of_existing_ports > 0) + for index in (0).upto(number_of_existing_ports) + port_configuration = config.serial_ports[index] + puts "port_configuration[#{index}]=#{port_configuration.inspect}" + + #there may be no configuration for this port so dont change it, if this is the case + if !port_configuration.nil? + port = current_ports[index] + port = configure_serial_port(dc, port_configuration, port) + + edit_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => port + } + + spec[:config][:deviceChange].push edit_port + spec[:config][:deviceChange].uniq! + end + end + end + + #add extra network interfaces + for index in (number_of_existing_ports).upto(config_serial_ports_length-1) + port_configuration = config.serial_ports[index] + adapter = RbVmomi::VIM::VirtualSerialPort( + :key => index, + :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() + ) + + adapter = configure_serial_port(dc, port_configuration, adapter) + + add_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), + :device => adapter + } + + spec[:config][:deviceChange].push add_port + spec[:config][:deviceChange].uniq! + end + + puts "spec[:config] = #{spec[:config].inspect}" + + spec + end + + def configure_serial_port(dc, port_configuration, port) + port.yieldOnPoll = port_configuration.yieldOnPoll unless port_configuration.yieldOnPoll.nil? + port.connectable.connected = port_configuration.connected unless port_configuration.connected.nil? + port.connectable.startConnected = port_configuration.startConnected unless port_configuration.startConnected.nil? + + case port_configuration.backing + when 'uri' + port.backing = RbVmomi::VIM::VirtualSerialPortURIBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortURIBackingInfo) + port.backing.direction = port_configuration.direction unless port_configuration.direction.nil? + port.backing.proxyURI = port_configuration.proxyURI unless port_configuration.proxyURI.nil? + port.backing.serviceURI = port_configuration.serviceURI unless port_configuration.serviceURI.nil? + when 'pipe' + port.backing = RbVmomi::VIM::VirtualSerialPortPipeBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortPipeBackingInfo) + port.backing.endpoint = port_configuration.endpoint unless port_configuration.endpoint.nil? + port.backing.noRxLoss = port_configuration.noRxLoss unless port_configuration.noRxLoss.nil? + when 'file' + port.backing = RbVmomi::VIM::VirtualSerialPortFileBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortFileBackingInfo) + port.backing.fileName = port_configuration.fileName unless port_configuration.fileName.nil? + when 'device' + port.backing = RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo) + port.backing.deviceName = port_configuration.deviceName unless port_configuration.deviceName.nil? + port.backing.useAutoDetect = port_configuration.useAutoDetect unless port_configuration.useAutoDetect.nil? + end + + port + end + def configure_network_cards(spec, dc, template, config) #Enumerate lan cards spec[:config][:deviceChange] ||= [] From b2a54204d0a69eade02eedbcd35904ebcae300df Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 12:08:41 +0000 Subject: [PATCH 20/39] Allow management nic to be specified Allow ip family to detect on management nic to be specified Allow an ip address to be specified for a nic, so that auto detection of ip does not take place (use in situations where you cant install vmware guest tools) Use default nic Vsphere nic detection if we not using a customized approach Use standard variable naming convention --- README.md | 41 ++++++++++++--------- lib/vSphere/config.rb | 85 ++++++++++++++++++++++--------------------- lib/vSphere/driver.rb | 54 ++++++++++++++++++--------- locales/en.yml | 4 +- 4 files changed, 106 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 02ee9271..72917096 100644 --- a/README.md +++ b/README.md @@ -141,32 +141,39 @@ This provider has the following settings, all are required unless noted: where the key must start with `guestinfo.`. VMs with VWware Tools installed can retrieve the value of these variables using the `vmtoolsd` command: `vmtoolsd --cmd 'info-get guestinfo.some.variable'`. * `notes` - _Optional_ string - Add arbitrary notes to the VM -* `real_nic_ip` - _Optional_ boolean - Enable logic that forces the acquisition of the ssh IP address - for a target VM to be retrieved from the list of vm adapters on the host and filtered for a single legitimate - adapter with a defined interface. An error will be raised if this filter is enabled and multiple valid - adapters exist on a host. +* `management_network_adapter_slot` - _Optional_ integer - zero based array of the card index. + This will be the network card to get the ip address from to use for communication between + Vagrant and the vm. If this is not set we will use the one detected by VSphere. +* `management_network_adapter_address_family` - _Optional_ string - When auto detecting ip address to use for + communication only detect specified ip address family. Possible values are 'ipv4' and 'ipv6'. If this value + is not set it will use the first ip address detected. * `destroy_unused_network_interfaces` - _Optional_ boolean - should network cards that have not been configured explicitly, be deleted. If set to false then existing network cards are left alone. * `network_adapter` - Array of network card configuration +* `destroy_unused_serial_ports` - _Optional_ boolean - should serial ports that have not been configured + explicitly, be deleted. If set to false then existing serial ports are left alone. * `serial_port` - Array of serial port configuration ## Network card configuration * `slot` - integer - zero based array of the card index -* `allowGuestControl` - _Optional_ boolean - Configure the address type of the +* `allow_guest_control` - _Optional_ boolean - Configure the address type of the * `connected` - _Optional_ boolean - is the vm network card connected to the network -* `startConnected` - _Optional_ boolean - When VM is turned on should the vm network card be connected to the network +* `start_connected` - _Optional_ boolean - When VM is turned on should the vm network card be connected to the network * `vlan` - _Optional_ string - vlan to connect the network card to -* `addressType` - _Optional_ - Configure the address type of the +* `address_type` - _Optional_ - Configure the address type of the [vSphere Virtual Ethernet Card](https://www.vmware.com/support/developer/vc-sdk/visdk2xpubs/ReferenceGuide/vim.vm.device.VirtualEthernetCard.html) -* `macAddress` - _Optional_ string - Used to set the mac address of the new VM -* `wakeOnLanEnabled` - _Optional_ boolean - should vm turn on when magic packet is received on network card +* `mac_address` - _Optional_ string - Used to set the mac address of the network card +* `ip_address` - _Optional_ string - Do not auto detect the ip address for this network card, assume it is the ip + address specified. Use this when guest tools cannot be installed on the vm. One approach is to specify static mac address + for vm and reserve ip address on DHCP server for mac address. +* `wake_on_lan_enabled` - _Optional_ boolean - should vm turn on when magic packet is received on network card ## Serial port configuration -* `yieldOnPoll` - _Optional_ boolean - Enables CPU yield behavior. If you set yieldOnPoll to true, the virtual machine will +* `yield_on_poll` - _Optional_ boolean - Enables CPU yield behavior. If you set yieldOnPoll to true, the virtual machine will periodically relinquish the processor if its sole task is polling the virtual serial port. The amount of time it takes to regain the processor will depend on the degree of other virtual machine activity on the host. * `connected` - _Optional_ boolean - is the vm serial port connected -* `startConnected` - _Optional_ boolean - When VM is turned on should the vm serial port be connected +* `start_connected` - _Optional_ boolean - When VM is turned on should the vm serial port be connected * `backing` - _Optional_ string - The type of serial port backing to use. Possible values are 'uri', 'pipe', 'file', 'device'. `uri` supports a connection between the virtual machine and a resource on the network. The virtual machine can initiate a connection @@ -180,9 +187,9 @@ This provider has the following settings, all are required unless noted: ### uri * `direction` - _Optional_ string - The direction of the connection. Possible values are 'client' and 'server' -* `proxyURI` - _Optional_ string - Identifies a proxy service that provides network access to the serviceURI. If you specify +* `proxy_uri` - _Optional_ string - Identifies a proxy service that provides network access to the serviceURI. If you specify a proxy URI, the virtual machine initiates a connection with the proxy service and forwards the serviceURI and direction to the proxy. -* `serviceURI` - _Optional_ string - Identifies the local host or a system on the network, depending on the value of +* `service_uri` - _Optional_ string - Identifies the local host or a system on the network, depending on the value of direction. If you use the virtual machine as a server, the URI identifies the host on which the virtual machine runs. In this case, the host name part of the URI should be empty, or it should specify the address of the local host. If you use the virtual machine as a client, the URI identifies the remote system on the network. @@ -190,17 +197,17 @@ This provider has the following settings, all are required unless noted: ### pipe * `endpoint` - _Optional_ string - Indicates the role the virtual machine assumes as an endpoint for the pipe. Possible values are 'client' and 'server' -* `noRxLoss` - _Optional_ boolean - Enables optimized data transfer over the pipe. When you use this feature, +* `no_rx_loss` - _Optional_ boolean - Enables optimized data transfer over the pipe. When you use this feature, the ESX server buffers data to prevent data overrun. This allows the virtual machine to read all of the data transferred over the pipe with no data loss. To use optimized data transfer, set noRxLoss to true. To disable this feature, set the property to false. ### file -* `fileName` - _Optional_ string - Filename for the host file used in this backing. +* `file_name` - _Optional_ string - Filename for the host file used in this backing. ### device -* `deviceName` - _Optional_ string - The name of the device on the host system. -* `useAutoDetect` - _Optional_ boolean - Indicates whether the device should be auto detected instead of directly specified. If this value is set to TRUE, deviceName is ignored. +* `device_name` - _Optional_ string - The name of the device on the host system. +* `use_auto_detect` - _Optional_ boolean - Indicates whether the device should be auto detected instead of directly specified. If this value is set to TRUE, deviceName is ignored. ### Cloning from a VM rather than a template diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 77ae1c62..302faba4 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -5,74 +5,77 @@ module VSphere class Config < Vagrant.plugin('2', :config) class NetworkConfiguration - attr_accessor :allowGuestControl + attr_accessor :allow_guest_control attr_accessor :connected - attr_accessor :startConnected + attr_accessor :start_connected attr_accessor :vlan - attr_accessor :addressType - attr_accessor :macAddress - attr_accessor :wakeOnLanEnabled + attr_accessor :address_type + attr_accessor :mac_address + attr_accessor :ip_address + attr_accessor :wake_on_lan_enabled def initialize(network_config) - @allowGuestControl = false + @allow_guest_control = false @connected = true - @startConnected = true + @start_connected = true @vlan = nil - @addressType = 'generated' - @macAddress = nil - @wakeOnLanEnabled = false + @address_type = 'generated' + @mac_address = nil + @ip_address = nil + @wake_on_lan_enabled = false - @allowGuestControl = network_config[:allowGuestControl] if network_config.key?(:allowGuestControl) + @allow_guest_control = network_config[:allow_guest_control] if network_config.key?(:allow_guest_control) @connected = network_config[:connected] if network_config.key?(:connected) - @startConnected = network_config[:startConnected] if network_config.key?(:startConnected) + @start_connected = network_config[:start_connected] if network_config.key?(:start_connected) @vlan = network_config[:vlan] if network_config.key?(:vlan) - @addressType = network_config[:addressType] if network_config.key?(:addressType) - @macAddress = network_config[:macAddress] if network_config.key?(:macAddress) - @wakeOnLanEnabled = network_config[:wakeOnLanEnabled] if network_config.key?(:wakeOnLanEnabled) + @address_type = network_config[:address_type] if network_config.key?(:address_type) + @mac_address = network_config[:mac_address] if network_config.key?(:mac_address) + @ip_address = network_config[:ip_address] if network_config.key?(:ip_address) + @wake_on_lan_enabled = network_config[:wake_on_lan_enabled] if network_config.key?(:wake_on_lan_enabled) end end class SerialPortConfiguration - attr_accessor :yieldOnPoll + attr_accessor :yield_on_poll attr_accessor :connected - attr_accessor :startConnected + attr_accessor :start_connected attr_accessor :backing attr_accessor :direction - attr_accessor :proxyURI - attr_accessor :serviceURI + attr_accessor :proxy_uri + attr_accessor :service_uri attr_accessor :endpoint - attr_accessor :noRxLoss + attr_accessor :no_rx_loss - attr_accessor :fileName + attr_accessor :file_name - attr_accessor :deviceName - attr_accessor :useAutoDetect + attr_accessor :device_name + attr_accessor :use_auto_detect def initialize(serial_port_config) - @yieldOnPoll = true + @yield_on_poll = true @connected = true - @startConnected = true + @start_connected = true @backing = '' @direction = '' - @proxyURI = '' - @serviceURI = '' + @proxy_uri = '' + @service_uri = '' @endpoint = '' - @noRxLoss = true + @no_rx_loss = true - @fileName = '' + @file_name = '' - @deviceName = '' - @useAutoDetect = false + @device_name = '' + @use_auto_detect = false - @yieldOnPoll = serial_port_config[:yieldOnPoll] if serial_port_config.key?(:yieldOnPoll) + @yield_on_poll = serial_port_config[:yield_on_poll] if serial_port_config.key?(:yield_on_poll) @connected = network_config[:connected] if network_config.key?(:connected) - @startConnected = network_config[:startConnected] if network_config.key?(:startConnected) + @start_connected = network_config[:start_connected] if network_config.key?(:start_connected) @backing = serial_port_config[:backing] if serial_port_config.key?(:backing) if !(@backing == 'uri' || @backing == 'pipe' || @backing == 'file' || @backing == 'device') raise "The only valid values allowed for backing are 'uri', 'pipe', 'file', 'device'" @@ -82,19 +85,19 @@ def initialize(serial_port_config) if @backing == 'uri' && !(@direction == 'client' || @direction == 'server') raise "The only valid values allowed for direction are 'client', 'server'" end - @proxyURI = serial_port_config[:proxyURI] if serial_port_config.key?(:proxyURI) - @serviceURI = serial_port_config[:serviceURI] if serial_port_config.key?(:serviceURI) + @proxy_uri = serial_port_config[:proxy_uri] if serial_port_config.key?(:proxy_uri) + @service_uri = serial_port_config[:service_uri] if serial_port_config.key?(:service_uri) @endpoint = serial_port_config[:endpoint] if serial_port_config.key?(:endpoint) if @backing == 'pipe' && !(@endpoint == 'client' || @endpoint == 'server') raise "The only valid values allowed for endpoint are 'client', 'server'" end - @noRxLoss = serial_port_config[:noRxLoss] if serial_port_config.key?(:noRxLoss) + @no_rx_loss = serial_port_config[:no_rx_loss] if serial_port_config.key?(:no_rx_loss) - @fileName = serial_port_config[:fileName] if serial_port_config.key?(:fileName) + @file_name = serial_port_config[:file_name] if serial_port_config.key?(:file_name) - @deviceName = serial_port_config[:deviceName] if serial_port_config.key?(:deviceName) - @useAutoDetect = serial_port_config[:useAutoDetect] if serial_port_config.key?(:useAutoDetect) + @device_name = serial_port_config[:device_name] if serial_port_config.key?(:device_name) + @use_auto_detect = serial_port_config[:use_auto_detect] if serial_port_config.key?(:use_auto_detect) end end @@ -121,10 +124,10 @@ def initialize(serial_port_config) attr_accessor :extra_config attr_accessor :notes - attr_accessor :real_nic_ip - attr_accessor :destroy_unused_network_interfaces attr_accessor :destroy_unused_serial_ports + attr_accessor :management_network_adapter_slot + attr_accessor :management_network_adapter_address_family attr_reader :network_adapters attr_reader :serial_ports attr_reader :custom_attributes diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 8dc82dc5..b28c6aa6 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -1,5 +1,6 @@ require 'log4r' require 'rbvmomi' +require 'ipaddr' module VagrantPlugins module VSphere @@ -44,7 +45,6 @@ def ssh_info vm = get_vm_by_uuid conn, @machine return nil if vm.nil? return nil unless vm.runtime.powerState.eql?(VmState::POWERED_ON) - ip_address = filter_guest_nic(vm, @machine) return nil if ip_address.nil? || ip_address.empty? { @@ -388,10 +388,28 @@ def enumerate_snapshots(vm) end def filter_guest_nic(vm, machine) - return vm.guest.ipAddress unless machine.provider_config.real_nic_ip - ip_addresses = vm.guest.net.select { |g| g.deviceConfigId > 0 }.map { |g| g.ipAddress[0] } - fail Errors::VSphereError.new, :'multiple_interface_with_real_nic_ip_set' if ip_addresses.size > 1 - ip_addresses.first + config = machine.provider_config + + if config.management_network_adapter_slot.nil? + return vm.guest.ipAddress + elsif config.network_adapters[config.management_network_adapter_slot].nil? || config.network_adapters[config.management_network_adapter_slot].ip_address.nil? + fail Errors::VSphereError.new, :'specified_mangement_interface_does_not_exist' unless config.management_network_adapter_slot < vm.guest.net.length + + ipAddress = nil + case config.management_network_adapter_address_family + when 'ipv4' + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv4? && addr.origin != 'linklayer' } + when 'ipv6' + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv6? && addr.origin != 'linklayer' } + else + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| addr.origin != 'linklayer' } + end + + return nil if ipAddress.nil? + return ipAddress.ipAddress + else + config.network_adapters[config.management_network_adapter_slot].ip_address + end end def get_datacenter(connection, machine) @@ -681,27 +699,27 @@ def configure_serial_ports(spec, dc, template, config) end def configure_serial_port(dc, port_configuration, port) - port.yieldOnPoll = port_configuration.yieldOnPoll unless port_configuration.yieldOnPoll.nil? + port.yieldOnPoll = port_configuration.yield_on_poll unless port_configuration.yield_on_poll.nil? port.connectable.connected = port_configuration.connected unless port_configuration.connected.nil? - port.connectable.startConnected = port_configuration.startConnected unless port_configuration.startConnected.nil? + port.connectable.startConnected = port_configuration.start_connected unless port_configuration.start_connected.nil? case port_configuration.backing when 'uri' port.backing = RbVmomi::VIM::VirtualSerialPortURIBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortURIBackingInfo) port.backing.direction = port_configuration.direction unless port_configuration.direction.nil? - port.backing.proxyURI = port_configuration.proxyURI unless port_configuration.proxyURI.nil? - port.backing.serviceURI = port_configuration.serviceURI unless port_configuration.serviceURI.nil? + port.backing.proxyURI = port_configuration.proxy_uri unless port_configuration.proxy_uri.nil? + port.backing.serviceURI = port_configuration.service_uri unless port_configuration.service_uri.nil? when 'pipe' port.backing = RbVmomi::VIM::VirtualSerialPortPipeBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortPipeBackingInfo) port.backing.endpoint = port_configuration.endpoint unless port_configuration.endpoint.nil? - port.backing.noRxLoss = port_configuration.noRxLoss unless port_configuration.noRxLoss.nil? + port.backing.noRxLoss = port_configuration.no_rx_loss unless port_configuration.no_rx_loss.nil? when 'file' port.backing = RbVmomi::VIM::VirtualSerialPortFileBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortFileBackingInfo) - port.backing.fileName = port_configuration.fileName unless port_configuration.fileName.nil? + port.backing.fileName = port_configuration.file_name unless port_configuration.file_name.nil? when 'device' port.backing = RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo) - port.backing.deviceName = port_configuration.deviceName unless port_configuration.deviceName.nil? - port.backing.useAutoDetect = port_configuration.useAutoDetect unless port_configuration.useAutoDetect.nil? + port.backing.deviceName = port_configuration.device_name unless port_configuration.device_name.nil? + port.backing.useAutoDetect = port_configuration.use_auto_detect unless port_configuration.use_auto_detect.nil? end port @@ -833,13 +851,13 @@ def configure_network_card(dc, adapter_configuration, adapter, label, summary) adapter.deviceInfo.label = label unless label.nil? adapter.deviceInfo.summary = summary unless summary.nil? - adapter.connectable.allowGuestControl = adapter_configuration.allowGuestControl unless adapter_configuration.allowGuestControl.nil? + adapter.connectable.allowGuestControl = adapter_configuration.allow_guest_control unless adapter_configuration.allow_guest_control.nil? adapter.connectable.connected = adapter_configuration.connected unless adapter_configuration.connected.nil? - adapter.connectable.startConnected = adapter_configuration.startConnected unless adapter_configuration.startConnected.nil? + adapter.connectable.startConnected = adapter_configuration.start_connected unless adapter_configuration.start_connected.nil? - adapter.addressType = adapter_configuration.addressType unless adapter_configuration.addressType.nil? - adapter.macAddress = adapter_configuration.macAddress unless adapter_configuration.macAddress.nil? - adapter.wakeOnLanEnabled = adapter_configuration.wakeOnLanEnabled unless adapter_configuration.wakeOnLanEnabled.nil? + adapter.addressType = adapter_configuration.address_type unless adapter_configuration.address_type.nil? + adapter.macAddress = adapter_configuration.mac_address unless adapter_configuration.mac_address.nil? + adapter.wakeOnLanEnabled = adapter_configuration.wake_on_lan_enabled unless adapter_configuration.wake_on_lan_enabled.nil? adapter end diff --git a/locales/en.yml b/locales/en.yml index 79ece691..b4275049 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -48,8 +48,8 @@ en: Cannot find network card to customize invalid_configuration_linked_clone_with_sdrs: |- Cannot use Linked Clone with Storage DRS - multiple_interface_with_real_nic_ip_set: |- - real_nic_ip filtering set with multiple valid VM interfaces available + specified_mangement_interface_does_not_exist: |- + specified management interface does not exist sysprep_timeout: |- Customization of VM not succeeded within timeout. wait_for_ip_address_timeout: |- From 3512fe466eb00baeb3f9816f986320bec6785517 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 14:26:49 +0000 Subject: [PATCH 21/39] Allow IPAddr to be used to specify ip address --- lib/vSphere/driver.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index b28c6aa6..28a64705 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -408,7 +408,8 @@ def filter_guest_nic(vm, machine) return nil if ipAddress.nil? return ipAddress.ipAddress else - config.network_adapters[config.management_network_adapter_slot].ip_address + return config.network_adapters[config.management_network_adapter_slot].ip_address.ipAddress if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) + return config.network_adapters[config.management_network_adapter_slot].ip_address end end From 49b34d11dbc719d3cf9193f1ae93a1da8d5cb1c5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 14:51:11 +0000 Subject: [PATCH 22/39] Use correct to string method --- lib/vSphere/driver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 28a64705..cdbc5633 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -408,7 +408,7 @@ def filter_guest_nic(vm, machine) return nil if ipAddress.nil? return ipAddress.ipAddress else - return config.network_adapters[config.management_network_adapter_slot].ip_address.ipAddress if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) + return config.network_adapters[config.management_network_adapter_slot].ip_address.to_s if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) return config.network_adapters[config.management_network_adapter_slot].ip_address end end From 71f43e3f53b75f4a771f78fc691c2a35849e7ce6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 16:13:29 +0000 Subject: [PATCH 23/39] If we specify a mac address then default address_type to manual --- lib/vSphere/config.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 302faba4..62450fb7 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -32,6 +32,7 @@ def initialize(network_config) @vlan = network_config[:vlan] if network_config.key?(:vlan) @address_type = network_config[:address_type] if network_config.key?(:address_type) @mac_address = network_config[:mac_address] if network_config.key?(:mac_address) + @address_type = 'manual' if network_config.key?(:mac_address) @ip_address = network_config[:ip_address] if network_config.key?(:ip_address) @wake_on_lan_enabled = network_config[:wake_on_lan_enabled] if network_config.key?(:wake_on_lan_enabled) end From 1f41afd6d9b618c599084aab680c9716797d0ebf Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 16:30:32 +0000 Subject: [PATCH 24/39] Take mac format in multiple formats and convert to what Vsphere expects --- lib/vSphere/config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 62450fb7..c66f0b5f 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -31,7 +31,7 @@ def initialize(network_config) @start_connected = network_config[:start_connected] if network_config.key?(:start_connected) @vlan = network_config[:vlan] if network_config.key?(:vlan) @address_type = network_config[:address_type] if network_config.key?(:address_type) - @mac_address = network_config[:mac_address] if network_config.key?(:mac_address) + @mac_address = network_config[:mac_address].tr(' |-', '').gsub(/(..)(..)(..)(..)(..)(..)/, '\1:\2:\3:\4:\5:\6') if network_config.key?(:mac_address) @address_type = 'manual' if network_config.key?(:mac_address) @ip_address = network_config[:ip_address] if network_config.key?(:ip_address) @wake_on_lan_enabled = network_config[:wake_on_lan_enabled] if network_config.key?(:wake_on_lan_enabled) From 57e94bb445a9e2cd2d01a0f10b134646143e8263 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 17 Nov 2016 18:17:12 +0000 Subject: [PATCH 25/39] Wait for sys prep is now renamed wait for customization. Customization can occur on other operating systems. You can choose to wait for customization to complete, as not everyone uses VSphere customizations. --- README.md | 4 ++++ lib/vSphere/config.rb | 3 +++ lib/vSphere/driver.rb | 33 +++++++++++++++++++++++++++++---- locales/en.yml | 8 +++----- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 72917096..8d10e78d 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,10 @@ This provider has the following settings, all are required unless noted: where the key must start with `guestinfo.`. VMs with VWware Tools installed can retrieve the value of these variables using the `vmtoolsd` command: `vmtoolsd --cmd 'info-get guestinfo.some.variable'`. * `notes` - _Optional_ string - Add arbitrary notes to the VM +* `wait_for_customization` - _Optional_ boolean - Wait for customization to complete before + continuing. Set to false by default. +* `wait_for_customization_timeout` - _Optional_ integer - Timeout in seconds to wait for + customization to complete before continuing. Set to 600 by default. * `management_network_adapter_slot` - _Optional_ integer - zero based array of the card index. This will be the network card to get the ip address from to use for communication between Vagrant and the vm. If this is not set we will use the one detected by VSphere. diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index c66f0b5f..435f5a0f 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -124,6 +124,8 @@ def initialize(serial_port_config) attr_accessor :mem_reservation attr_accessor :extra_config attr_accessor :notes + attr_accessor :wait_for_customization + attr_accessor :wait_for_customization_timeout attr_accessor :destroy_unused_network_interfaces attr_accessor :destroy_unused_serial_ports @@ -134,6 +136,7 @@ def initialize(serial_port_config) attr_reader :custom_attributes def initialize + wait_for_customization_timeout = 600 @destroy_unused_network_interfaces = UNSET_VALUE @destroy_unused_serial_ports = UNSET_VALUE @network_adapters = {} diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index cdbc5633..ad2b6c52 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -210,11 +210,36 @@ def clone(root_path) new_vm = task.wait_for_completion end @logger.info("Finished cloning vm #{@machine.id}") + end - config.custom_attributes.each do |k, v| - new_vm.setCustomValue(key: k, value: v) - end + config.custom_attributes.each do |k, v| + new_vm.setCustomValue(key: k, value: v) end + + if config.wait_for_customization + @logger.info I18n.t('vsphere.wait_for_customization') + vem = connection.serviceContent.eventManager + + wait = true + waited_seconds = 0 + sleep_time = 5 + + while wait + events = vem.QueryEvents(filter:RbVmomi::VIM::EventFilterSpec(entity:RbVmomi::VIM::EventFilterSpecByEntity(entity: new_vm, recursion:RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) + + if events.size > 0 + events.each do |e| + @logger.info e.fullFormattedMessage + end + wait = false + elsif waited_seconds >= config.wait_for_customization_timeout + fail Errors::VSphereError, :'customization_timeout' + else + sleep(sleep_time) + waited_seconds += sleep_time + end + end + end rescue Errors::VSphereError raise #rescue StandardError => e @@ -540,7 +565,7 @@ def get_customization_spec(machine, spec_info) end customization_spec - end + end def get_location(datastore, dc, machine, template) if machine.provider_config.linked_clone diff --git a/locales/en.yml b/locales/en.yml index b4275049..e0e43346 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -20,8 +20,8 @@ en: The VM has not been created vm_not_running: |- The VM is not running - wait_sysprep: |- - Waiting for sysprep + wait_for_customization: |- + Waiting for customization errors: missing_template: |- @@ -50,10 +50,8 @@ en: Cannot use Linked Clone with Storage DRS specified_mangement_interface_does_not_exist: |- specified management interface does not exist - sysprep_timeout: |- + customization_timeout: |- Customization of VM not succeeded within timeout. - wait_for_ip_address_timeout: |- - Timeout while waiting for ip address config: host: |- From 55c8916e676cd14dd9e704ccd0a6c99ea27e8003 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 21 Nov 2016 10:28:02 +0000 Subject: [PATCH 26/39] id is not used anywhere --- lib/vSphere/provider.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/vSphere/provider.rb b/lib/vSphere/provider.rb index 10ff3e1a..1d05beff 100644 --- a/lib/vSphere/provider.rb +++ b/lib/vSphere/provider.rb @@ -25,7 +25,6 @@ def action(name) # If the machine ID changed, then we need to rebuild our underlying # driver. def machine_id_changed - id = @machine.id @logger.debug("Instantiating the driver for machine ID: #{@machine.id.inspect}") @driver = VagrantPlugins::VSphere::Driver.new(@machine) nil From 41046c1761858e5c207a4d0ea918fb11d8739c73 Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Thu, 17 Nov 2016 17:59:06 -0700 Subject: [PATCH 27/39] Add instructions on using rubocop to DEVELOPMENT.md --- DEVELOPMENT.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2f0ba850..630f9c04 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -30,6 +30,19 @@ Changes that eliminate rules from [`.rubocop_todo.yml`](https://github.com/nsidc/vagrant-vsphere/blob/master/.rubocop_todo.yml) are welcome. +To run RuboCop: + +``` +bundle exec rake rubocop +``` + +You can run RuboCop with its `--auto-correct` feature to correct simple +violations. When using this option, you should still double-check your code to +be certain none of your logic was changed. To run RuboCop with `--auto-correct`: + +``` +bundle exec rake rubocop:auto_correct +``` ### Travis-CI [Travis](https://travis-ci.org/nsidc/vagrant-vsphere) will automatically run From 7793599c1593cc4e231171b2942e03119e1c5e0d Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Thu, 17 Nov 2016 17:51:02 -0700 Subject: [PATCH 28/39] Replace tabs with 2 spaces --- lib/vSphere/driver.rb | 1830 ++++++++++++++++++++--------------------- 1 file changed, 915 insertions(+), 915 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index ad2b6c52..67b7fb43 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -3,918 +3,918 @@ require 'ipaddr' module VagrantPlugins - module VSphere - module VmState - POWERED_ON = 'poweredOn' - POWERED_OFF = 'poweredOff' - SUSPENDED = 'suspended' - end - - class Driver - attr_reader :logger - attr_reader :machine - - def initialize(machine) - @logger = Log4r::Logger.new("vagrant::provider::vsphere::driver") - @machine = machine - end - - def connection - raise "connection be called from a code block!" if !block_given? - - begin - config = @machine.provider_config - - current_connection = RbVmomi::VIM.connect host: config.host, - user: config.user, password: config.password, - insecure: config.insecure, proxyHost: config.proxy_host, - proxyPort: config.proxy_port - - yield current_connection - rescue - raise - ensure - current_connection.close if current_connection - end - end - - def ssh_info - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - return nil if vm.nil? - return nil unless vm.runtime.powerState.eql?(VmState::POWERED_ON) - ip_address = filter_guest_nic(vm, @machine) - return nil if ip_address.nil? || ip_address.empty? - { - host: ip_address, - port: 22 - } - end - end - - def state - return :not_created if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - - return :not_created if vm.nil? - - if powered_on? - :running - else - # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM - :poweroff - end - end - end - - def power_on_vm - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start powering on vm #{@machine.id}") - vm.PowerOnVM_Task.wait_for_completion - @logger.info("Finished powering on vm #{@machine.id}") - end - end - - def power_off_vm - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start powering off vm #{@machine.id}") - vm.PowerOffVM_Task.wait_for_completion - @logger.info("Finished powering off vm #{@machine.id}") - end - end - - def get_vm_state - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState - end - end - - def powered_on? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::POWERED_ON) - end - end - - def powered_off? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::POWERED_OFF) - end - end - - def suspended? - return nil if @machine.id.nil? - connection do |conn| - vm = get_vm_by_uuid conn, @machine - vm.runtime.powerState.eql?(VmState::SUSPENDED) - end - end - - def clone(root_path) - config = machine.provider_config - connection do |conn| - name = get_name @machine, config, root_path - dc = get_datacenter conn, @machine - template = dc.find_vm config.template_name - fail Errors::VSphereError, :'missing_template' if template.nil? - vm_base_folder = get_vm_base_folder dc, template, config - fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? - - begin - # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf - ds = get_datastore dc, @machine - fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) - - location = get_location ds, dc, @machine, template - - spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false - spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec - customization_info = get_customization_spec_info_by_name conn, @machine - spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? - - spec = configure_network_cards(spec, dc, template, config) - - add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? - add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? - add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? - add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? - add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? - add_custom_notes(spec, config.notes) unless config.notes.nil? - - if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) - - storage_mgr = conn.serviceContent.storageResourceManager - pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) - # TODO: May want to add option on type? - storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) - - @logger.info(I18n.t('vsphere.requesting_sdrs_recommendation')) - @logger.info(" -- DatastoreCluster: #{ds.name}") - @logger.info(" -- Template VM: #{template.pretty_path}") - @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") - - result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) - - recommendation = result.recommendations[0] - key = recommendation.key ||= '' - if key == '' - fail Errors::VSphereError, :missing_datastore_recommendation - end - - @logger.info(I18n.t('vsphere.creating_cloned_vm_sdrs')) - @logger.info(" -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}") - - @logger.info("Start cloning vm #{@machine.id}") - task = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]) - - apply_sr_result = nil - if block_given? - apply_sr_result = task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - apply_sr_result = task.wait_for_completion - end - @logger.info("Finished cloning vm #{@machine.id}") - - new_vm = apply_sr_result.vm - else - @logger.info(I18n.t('vsphere.creating_cloned_vm')) - @logger.info(" -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}") - @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") - - @logger.info("Start cloning vm #{@machine.id}") - task = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec) - new_vm = nil - if block_given? - new_vm = task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - new_vm = task.wait_for_completion - end - @logger.info("Finished cloning vm #{@machine.id}") - end - - config.custom_attributes.each do |k, v| - new_vm.setCustomValue(key: k, value: v) - end - - if config.wait_for_customization - @logger.info I18n.t('vsphere.wait_for_customization') - vem = connection.serviceContent.eventManager - - wait = true - waited_seconds = 0 - sleep_time = 5 - - while wait - events = vem.QueryEvents(filter:RbVmomi::VIM::EventFilterSpec(entity:RbVmomi::VIM::EventFilterSpecByEntity(entity: new_vm, recursion:RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) - - if events.size > 0 - events.each do |e| - @logger.info e.fullFormattedMessage - end - wait = false - elsif waited_seconds >= config.wait_for_customization_timeout - fail Errors::VSphereError, :'customization_timeout' - else - sleep(sleep_time) - waited_seconds += sleep_time - end - end - end - rescue Errors::VSphereError - raise - #rescue StandardError => e - # raise Errors::VSphereError.new, e.message - end - - # TODO: handle interrupted status in the environment, should the vm be destroyed? - @machine.id = new_vm.config.uuid - end - end - - def destroy - return nil if @machine.id.nil? - return nil unless is_created - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start destroying vm #{@machine.id}") - task = vm.Destroy_Task - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - @logger.info("Finished destroying vm #{@machine.id}") - end - - @machine.id = nil - end - - def is_created - return false if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - return false if vm.nil? - end - - true - end - - def is_running - state == :running - end - - def snapshot_list - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - @logger.info("Start destroying vm #{@machine.id}") - snapshots = enumerate_snapshots(vm).map(&:name) - @logger.info("Finished destroying vm #{@machine.id}") - return snapshots - end - end - - def delete_snapshot(snapshot_name) - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - - snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } - - # No snapshot matching "name" - return nil if snapshot.nil? - - task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) - - @logger.info("Start deleting snapshot #{snapshot_name} on vm #{@machine.id}") - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - @logger.info("Finished deleting snapshot #{snapshot_name} on vm #{@machine.id}") - end - end - - def restore_snapshot(snapshot_name) - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - - snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } - - # No snapshot matching "name" - return nil if snapshot.nil? - - task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) - - @logger.info("Start restoring snapshot #{snapshot_name} on vm #{@machine.id}") - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - @logger.info("Finished restoring snapshot #{snapshot_name} on vm #{@machine.id}") - end - end - - def create_snapshot(snapshot_name) - return nil if @machine.id.nil? - - connection do |conn| - vm = get_vm_by_uuid conn, @machine - - task = vm.CreateSnapshot_Task( - name: name, - memory: false, - quiesce: false) - - @logger.info("Start creating snapshot #{snapshot_name} on vm #{@machine.id}") - - if block_given? - task.wait_for_progress do |progress| - yield progress unless progress.nil? - end - else - task.wait_for_completion - end - - @logger.info("Finished creating snapshot #{snapshot_name} on vm #{@machine.id}") - end - end - - private - - # Enumerate VM snapshot tree - # - # This method returns an enumerator that performs a depth-first walk - # of the VM snapshot grap and yields each VirtualMachineSnapshotTree - # node. - # - # @param vm [RbVmomi::VIM::VirtualMachine] - # - # @return [Enumerator] - def enumerate_snapshots(vm) - snapshot_info = vm.snapshot - - if snapshot_info.nil? - snapshot_root = [] - else - snapshot_root = snapshot_info.rootSnapshotList - end - - recursor = lambda do |snapshot_list| - Enumerator.new do |yielder| - snapshot_list.each do |s| - # Yield the current VirtualMachineSnapshotTree object - yielder.yield s - - # Recurse into child VirtualMachineSnapshotTree objects - children = recursor.call(s.childSnapshotList) - loop do - yielder.yield children.next - end - end - end - end - - recursor.call(snapshot_root) - end - - def filter_guest_nic(vm, machine) - config = machine.provider_config - - if config.management_network_adapter_slot.nil? - return vm.guest.ipAddress - elsif config.network_adapters[config.management_network_adapter_slot].nil? || config.network_adapters[config.management_network_adapter_slot].ip_address.nil? - fail Errors::VSphereError.new, :'specified_mangement_interface_does_not_exist' unless config.management_network_adapter_slot < vm.guest.net.length - - ipAddress = nil - case config.management_network_adapter_address_family - when 'ipv4' - ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv4? && addr.origin != 'linklayer' } - when 'ipv6' - ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv6? && addr.origin != 'linklayer' } - else - ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| addr.origin != 'linklayer' } - end - - return nil if ipAddress.nil? - return ipAddress.ipAddress - else - return config.network_adapters[config.management_network_adapter_slot].ip_address.to_s if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) - return config.network_adapters[config.management_network_adapter_slot].ip_address - end - end - - def get_datacenter(connection, machine) - connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) - end - - def get_vm_by_uuid(connection, machine) - get_datacenter(connection, machine).vmFolder.findByUuid machine.id - end - - def get_resource_pool(datacenter, machine) - rp = get_compute_resource(datacenter, machine) - - resource_pool_name = machine.provider_config.resource_pool_name || '' - - entity_array = resource_pool_name.split('/') - entity_array.each do |entity_array_item| - next if entity_array_item.empty? - if rp.is_a? RbVmomi::VIM::Folder - rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource - rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ResourcePool - rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) - elsif rp.is_a? RbVmomi::VIM::ComputeResource - rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) - else - fail Errors::VSphereError, :missing_resource_pool - end - end - rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) - rp - end - - def get_compute_resource(datacenter, machine) - cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) - fail Errors::VSphereError, :missing_compute_resource if cr.nil? - cr - end - - def find_clustercompute_or_compute_resource(datacenter, path) - if path.is_a? String - es = path.split('/').reject(&:empty?) - elsif path.is_a? Enumerable - es = path - else - fail "unexpected path class #{path.class}" - end - return datacenter.hostFolder if es.empty? - final = es.pop - - p = es.inject(datacenter.hostFolder) do |f, e| - f.find(e, RbVmomi::VIM::Folder) || return - end - - begin - if (x = p.find(final, RbVmomi::VIM::ComputeResource)) - x - elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) - x - end - rescue Exception - # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead - x = p.childEntity.find { |x2| x2.name == final } - if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) - x - else - puts 'ex unknown type ' + x.to_json - nil - end - end - end - - def get_customization_spec_info_by_name(connection, machine) - name = machine.provider_config.customization_spec_name - return if name.nil? || name.empty? - - manager = connection.serviceContent.customizationSpecManager - fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? - - spec = manager.GetCustomizationSpec(name: name) - fail Errors::VSphereError, :missing_configuration_spec if spec.nil? - - spec - end - - def get_datastore(datacenter, machine) - name = machine.provider_config.data_store_name - return if name.nil? || name.empty? - - # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) - datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) - end - - def get_network_by_name(dc, name) - base = dc.networkFolder - entity_array = name.split('/').reject(&:empty?) - entity_array.each do |item| - case base - when RbVmomi::VIM::Folder - base = base.find(item) - when RbVmomi::VIM::VmwareDistributedVirtualSwitch - idx = base.summary.portgroupName.find_index(item) - base = idx.nil? ? nil : base.portgroup[idx] - end - end - - fail(Errors::VSphereError, :missing_vlan) if base.nil? - - base - end - - #Cloning - def get_customization_spec(machine, spec_info) - customization_spec = spec_info.spec.clone - - # find all the configured private networks - private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } - return customization_spec if private_networks.nil? - - # make sure we have enough NIC settings to override with the private network settings - fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length - - # assign the private network IP to the NIC - private_networks.each_index do |idx| - customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] - end - - customization_spec - end - - def get_location(datastore, dc, machine, template) - if machine.provider_config.linked_clone - # The API for linked clones is quite strange. We can't create a linked - # straight from any VM. The disks of the VM for which we can create a - # linked clone need to be read-only and thus VC demands that the VM we - # are cloning from uses delta-disks. Only then it will allow us to - # share the base disk. - # - # Thus, this code first create a delta disk on top of the base disk for - # the to-be-cloned VM, if delta disks aren't used already. - disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) - disks.select { |disk| disk.backing.parent.nil? }.each do |disk| - spec = { - deviceChange: [ - { - operation: :remove, - device: disk - }, - { - operation: :add, - fileOperation: :create, - device: disk.dup.tap do |new_disk| - new_disk.backing = new_disk.backing.dup - new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" - new_disk.backing.parent = disk.backing - end - } - ] - } - template.ReconfigVM_Task(spec: spec).wait_for_completion - end - - location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) - elsif datastore.is_a? RbVmomi::VIM::StoragePod - location = RbVmomi::VIM.VirtualMachineRelocateSpec - else - location = RbVmomi::VIM.VirtualMachineRelocateSpec - - location[:datastore] = datastore unless datastore.nil? - end - location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm - location - end - - def get_name(machine, config, root_path) - return config.name unless config.name.nil? - - prefix = "#{root_path.basename}_#{machine.name}" - prefix.gsub!(/[^-a-z0-9_\.]/i, '') - # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs - prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" - end - - def get_vm_base_folder(dc, template, config) - if config.vm_base_path.nil? - template.parent - else - dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) - end - end - - def configure_serial_ports(spec, dc, template, config) - #Enumerate serial ports - spec[:config][:deviceChange] ||= [] - - current_ports = Hash.new - - template.config.hardware.device.grep(RbVmomi::VIM::VirtualSerialPort).each_with_index { |item, index| - current_ports[index] = item - } - - puts "config.serial_ports=#{config.serial_ports.inspect}" - - current_ports_length = current_ports.length - - config_serial_ports_length = -1 - config.serial_ports.each_with_index { |item, index| - if index > config_serial_ports_length - config_serial_ports_length = index - end - } - config_serial_ports_length += 1 - - #remove unused serial ports - if config.destroy_unused_serial_ports - if current_ports_length-1 > config_serial_ports_length-1 - for index in (current_ports_length-1).downto(config_serial_ports_length-1+1) - port = current_ports[index] - - remove_port = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), - :device => port - } - - spec[:config][:deviceChange].push remove_port - spec[:config][:deviceChange].uniq! - end - end - end - - #we have 5 ports but want 8 ports - #add 3 ports - #edit first 5 ports - number_of_existing_ports = current_ports_length - if current_ports_length > config_serial_ports_length - #we have 5 ports but want 3 ports - #remove 2 ports - #edit first 3 ports - number_of_existing_ports = config_serial_ports_length - end - - #edit existing network interfaces - if (number_of_existing_ports > 0) - for index in (0).upto(number_of_existing_ports) - port_configuration = config.serial_ports[index] - puts "port_configuration[#{index}]=#{port_configuration.inspect}" - - #there may be no configuration for this port so dont change it, if this is the case - if !port_configuration.nil? - port = current_ports[index] - port = configure_serial_port(dc, port_configuration, port) - - edit_port = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), - :device => port - } - - spec[:config][:deviceChange].push edit_port - spec[:config][:deviceChange].uniq! - end - end - end - - #add extra network interfaces - for index in (number_of_existing_ports).upto(config_serial_ports_length-1) - port_configuration = config.serial_ports[index] - adapter = RbVmomi::VIM::VirtualSerialPort( - :key => index, - :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() - ) - - adapter = configure_serial_port(dc, port_configuration, adapter) - - add_port = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), - :device => adapter - } - - spec[:config][:deviceChange].push add_port - spec[:config][:deviceChange].uniq! - end - - puts "spec[:config] = #{spec[:config].inspect}" - - spec - end - - def configure_serial_port(dc, port_configuration, port) - port.yieldOnPoll = port_configuration.yield_on_poll unless port_configuration.yield_on_poll.nil? - port.connectable.connected = port_configuration.connected unless port_configuration.connected.nil? - port.connectable.startConnected = port_configuration.start_connected unless port_configuration.start_connected.nil? - - case port_configuration.backing - when 'uri' - port.backing = RbVmomi::VIM::VirtualSerialPortURIBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortURIBackingInfo) - port.backing.direction = port_configuration.direction unless port_configuration.direction.nil? - port.backing.proxyURI = port_configuration.proxy_uri unless port_configuration.proxy_uri.nil? - port.backing.serviceURI = port_configuration.service_uri unless port_configuration.service_uri.nil? - when 'pipe' - port.backing = RbVmomi::VIM::VirtualSerialPortPipeBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortPipeBackingInfo) - port.backing.endpoint = port_configuration.endpoint unless port_configuration.endpoint.nil? - port.backing.noRxLoss = port_configuration.no_rx_loss unless port_configuration.no_rx_loss.nil? - when 'file' - port.backing = RbVmomi::VIM::VirtualSerialPortFileBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortFileBackingInfo) - port.backing.fileName = port_configuration.file_name unless port_configuration.file_name.nil? - when 'device' - port.backing = RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo) - port.backing.deviceName = port_configuration.device_name unless port_configuration.device_name.nil? - port.backing.useAutoDetect = port_configuration.use_auto_detect unless port_configuration.use_auto_detect.nil? - end - - port - end - - def configure_network_cards(spec, dc, template, config) - #Enumerate lan cards - spec[:config][:deviceChange] ||= [] - - current_adapters = Hash.new - - template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| - current_adapters[index] = item - } - - puts "config.network_adapters=#{config.network_adapters.inspect}" - - current_adapters_length = current_adapters.length - - # configuration may have gaps in it, so configuration may look like this: - # vsphere.network_adapter 0, vlan: "vlan0" - # vsphere.network_adapter 1, vlan: "vlan1" - # vsphere.network_adapter 9, vlan: "vlan9" - config_network_adapters_length = -1 - config.network_adapters.each_with_index { |item, index| - if index > config_network_adapters_length - config_network_adapters_length = index - end - } - config_network_adapters_length += 1 - - #remove unused network interfaces - if config.destroy_unused_network_interfaces - if current_adapters_length-1 > config_network_adapters_length-1 - for index in (current_adapters_length-1).downto(config_network_adapters_length-1+1) - adapter = current_adapters[index] - - remove_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), - :device => adapter - } - - spec[:config][:deviceChange].push remove_adaptor - spec[:config][:deviceChange].uniq! - end - end - end - - #we have 5 cards but want 8 cards - #add 3 cards - #edit first 5 cards - number_of_existing_adapters = current_adapters_length - if current_adapters_length > config_network_adapters_length - #we have 5 cards but want 3 cards - #remove 2 cards - #edit first 3 cards - number_of_existing_adapters = config_network_adapters_length - end - - #edit existing network interfaces - if (number_of_existing_adapters > 0) - for index in (0).upto(number_of_existing_adapters) - adapter_configuration = config.network_adapters[index] - puts "adapter_configuration[#{index}]=#{adapter_configuration.inspect}" - - #there may be no configuration for this card so dont change it, if this is the case - if !adapter_configuration.nil? - adapter = current_adapters[index] - - label = "Ethernet #{index+1}" - summary = nil - summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - - adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) - - edit_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), - :device => adapter - } - - spec[:config][:deviceChange].push edit_adaptor - spec[:config][:deviceChange].uniq! - end - end - end - - #add extra network interfaces - for index in (number_of_existing_adapters).upto(config_network_adapters_length-1) - adapter_configuration = config.network_adapters[index] - adapter = RbVmomi::VIM::VirtualVmxnet3( - :key => index, - :deviceInfo => RbVmomi::VIM::Description(), - :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() - ) - - label = "Ethernet #{index+1}" - summary = nil - summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? - - adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) - - add_adaptor = { - :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), - :device => adapter - } - - spec[:config][:deviceChange].push add_adaptor - spec[:config][:deviceChange].uniq! - end - - puts "spec[:config] = #{spec[:config].inspect}" - - spec - end - - def configure_network_card(dc, adapter_configuration, adapter, label, summary) - if !adapter_configuration.vlan.nil? - network = get_network_by_name(dc, adapter_configuration.vlan) - - if network.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) - switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) - adapter.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) - else - # not connected to a distibuted switch? - adapter.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) - end - end - - adapter.deviceInfo.label = label unless label.nil? - adapter.deviceInfo.summary = summary unless summary.nil? - - adapter.connectable.allowGuestControl = adapter_configuration.allow_guest_control unless adapter_configuration.allow_guest_control.nil? - adapter.connectable.connected = adapter_configuration.connected unless adapter_configuration.connected.nil? - adapter.connectable.startConnected = adapter_configuration.start_connected unless adapter_configuration.start_connected.nil? - - adapter.addressType = adapter_configuration.address_type unless adapter_configuration.address_type.nil? - adapter.macAddress = adapter_configuration.mac_address unless adapter_configuration.mac_address.nil? - adapter.wakeOnLanEnabled = adapter_configuration.wake_on_lan_enabled unless adapter_configuration.wake_on_lan_enabled.nil? - - adapter - end - - def add_custom_memory(spec, memory_mb) - spec[:config][:memoryMB] = Integer(memory_mb) - end - - def add_custom_cpu(spec, cpu_count) - spec[:config][:numCPUs] = Integer(cpu_count) - end - - def add_custom_cpu_reservation(spec, cpu_reservation) - spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) - end - - def add_custom_mem_reservation(spec, mem_reservation) - spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) - end - - def add_custom_extra_config(spec, extra_config = {}) - return if extra_config.empty? - - # extraConfig must be an array of hashes with `key` and `value` - # entries. - spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } - end - - def add_custom_notes(spec, notes) - spec[:config][:annotation] = notes - end - end - end -end \ No newline at end of file + module VSphere + module VmState + POWERED_ON = 'poweredOn' + POWERED_OFF = 'poweredOff' + SUSPENDED = 'suspended' + end + + class Driver + attr_reader :logger + attr_reader :machine + + def initialize(machine) + @logger = Log4r::Logger.new("vagrant::provider::vsphere::driver") + @machine = machine + end + + def connection + raise "connection be called from a code block!" if !block_given? + + begin + config = @machine.provider_config + + current_connection = RbVmomi::VIM.connect host: config.host, + user: config.user, password: config.password, + insecure: config.insecure, proxyHost: config.proxy_host, + proxyPort: config.proxy_port + + yield current_connection + rescue + raise + ensure + current_connection.close if current_connection + end + end + + def ssh_info + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return nil if vm.nil? + return nil unless vm.runtime.powerState.eql?(VmState::POWERED_ON) + ip_address = filter_guest_nic(vm, @machine) + return nil if ip_address.nil? || ip_address.empty? + { + host: ip_address, + port: 22 + } + end + end + + def state + return :not_created if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + return :not_created if vm.nil? + + if powered_on? + :running + else + # If the VM is powered off or suspended, we consider it to be powered off. A power on command will either turn on or resume the VM + :poweroff + end + end + end + + def power_on_vm + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering on vm #{@machine.id}") + vm.PowerOnVM_Task.wait_for_completion + @logger.info("Finished powering on vm #{@machine.id}") + end + end + + def power_off_vm + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start powering off vm #{@machine.id}") + vm.PowerOffVM_Task.wait_for_completion + @logger.info("Finished powering off vm #{@machine.id}") + end + end + + def get_vm_state + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState + end + end + + def powered_on? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_ON) + end + end + + def powered_off? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::POWERED_OFF) + end + end + + def suspended? + return nil if @machine.id.nil? + connection do |conn| + vm = get_vm_by_uuid conn, @machine + vm.runtime.powerState.eql?(VmState::SUSPENDED) + end + end + + def clone(root_path) + config = machine.provider_config + connection do |conn| + name = get_name @machine, config, root_path + dc = get_datacenter conn, @machine + template = dc.find_vm config.template_name + fail Errors::VSphereError, :'missing_template' if template.nil? + vm_base_folder = get_vm_base_folder dc, template, config + fail Errors::VSphereError, :'invalid_base_path' if vm_base_folder.nil? + + begin + # Storage DRS does not support vSphere linked clones. http://www.vmware.com/files/pdf/techpaper/vsphere-storage-drs-interoperability.pdf + ds = get_datastore dc, @machine + fail Errors::VSphereError, :'invalid_configuration_linked_clone_with_sdrs' if config.linked_clone && ds.is_a?(RbVmomi::VIM::StoragePod) + + location = get_location ds, dc, @machine, template + + spec = RbVmomi::VIM.VirtualMachineCloneSpec location: location, powerOn: true, template: false + spec[:config] = RbVmomi::VIM.VirtualMachineConfigSpec + customization_info = get_customization_spec_info_by_name conn, @machine + spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? + + spec = configure_network_cards(spec, dc, template, config) + + add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? + add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? + add_custom_cpu_reservation(spec, config.cpu_reservation) unless config.cpu_reservation.nil? + add_custom_mem_reservation(spec, config.mem_reservation) unless config.mem_reservation.nil? + add_custom_extra_config(spec, config.extra_config) unless config.extra_config.empty? + add_custom_notes(spec, config.notes) unless config.notes.nil? + + if !config.clone_from_vm && ds.is_a?(RbVmomi::VIM::StoragePod) + + storage_mgr = conn.serviceContent.storageResourceManager + pod_spec = RbVmomi::VIM.StorageDrsPodSelectionSpec(storagePod: ds) + # TODO: May want to add option on type? + storage_spec = RbVmomi::VIM.StoragePlacementSpec(type: 'clone', cloneName: name, folder: vm_base_folder, podSelectionSpec: pod_spec, vm: template, cloneSpec: spec) + + @logger.info(I18n.t('vsphere.requesting_sdrs_recommendation')) + @logger.info(" -- DatastoreCluster: #{ds.name}") + @logger.info(" -- Template VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + result = storage_mgr.RecommendDatastores(storageSpec: storage_spec) + + recommendation = result.recommendations[0] + key = recommendation.key ||= '' + if key == '' + fail Errors::VSphereError, :missing_datastore_recommendation + end + + @logger.info(I18n.t('vsphere.creating_cloned_vm_sdrs')) + @logger.info(" -- Storage DRS recommendation: #{recommendation.target.name} #{recommendation.reasonText}") + + @logger.info("Start cloning vm #{@machine.id}") + task = storage_mgr.ApplyStorageDrsRecommendation_Task(key: [key]) + + apply_sr_result = nil + if block_given? + apply_sr_result = task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + apply_sr_result = task.wait_for_completion + end + @logger.info("Finished cloning vm #{@machine.id}") + + new_vm = apply_sr_result.vm + else + @logger.info(I18n.t('vsphere.creating_cloned_vm')) + @logger.info(" -- #{config.clone_from_vm ? 'Source' : 'Template'} VM: #{template.pretty_path}") + @logger.info(" -- Target VM: #{vm_base_folder.pretty_path}/#{name}") + + @logger.info("Start cloning vm #{@machine.id}") + task = template.CloneVM_Task(folder: vm_base_folder, name: name, spec: spec) + new_vm = nil + if block_given? + new_vm = task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + new_vm = task.wait_for_completion + end + @logger.info("Finished cloning vm #{@machine.id}") + end + + config.custom_attributes.each do |k, v| + new_vm.setCustomValue(key: k, value: v) + end + + if config.wait_for_customization + @logger.info I18n.t('vsphere.wait_for_customization') + vem = connection.serviceContent.eventManager + + wait = true + waited_seconds = 0 + sleep_time = 5 + + while wait + events = vem.QueryEvents(filter:RbVmomi::VIM::EventFilterSpec(entity:RbVmomi::VIM::EventFilterSpecByEntity(entity: new_vm, recursion:RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) + + if events.size > 0 + events.each do |e| + @logger.info e.fullFormattedMessage + end + wait = false + elsif waited_seconds >= config.wait_for_customization_timeout + fail Errors::VSphereError, :'customization_timeout' + else + sleep(sleep_time) + waited_seconds += sleep_time + end + end + end + rescue Errors::VSphereError + raise + #rescue StandardError => e + # raise Errors::VSphereError.new, e.message + end + + # TODO: handle interrupted status in the environment, should the vm be destroyed? + @machine.id = new_vm.config.uuid + end + end + + def destroy + return nil if @machine.id.nil? + return nil unless is_created + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + task = vm.Destroy_Task + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished destroying vm #{@machine.id}") + end + + @machine.id = nil + end + + def is_created + return false if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + return false if vm.nil? + end + + true + end + + def is_running + state == :running + end + + def snapshot_list + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + @logger.info("Start destroying vm #{@machine.id}") + snapshots = enumerate_snapshots(vm).map(&:name) + @logger.info("Finished destroying vm #{@machine.id}") + return snapshots + end + end + + def delete_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + + # No snapshot matching "name" + return nil if snapshot.nil? + + task = snapshot.snapshot.RemoveSnapshot_Task(removeChildren: false) + + @logger.info("Start deleting snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished deleting snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + def restore_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + snapshot = enumerate_snapshots(vm).find { |s| s.name == snapshot_name } + + # No snapshot matching "name" + return nil if snapshot.nil? + + task = snapshot.snapshot.RevertToSnapshot_Task(suppressPowerOn: true) + + @logger.info("Start restoring snapshot #{snapshot_name} on vm #{@machine.id}") + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + @logger.info("Finished restoring snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + def create_snapshot(snapshot_name) + return nil if @machine.id.nil? + + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + task = vm.CreateSnapshot_Task( + name: name, + memory: false, + quiesce: false) + + @logger.info("Start creating snapshot #{snapshot_name} on vm #{@machine.id}") + + if block_given? + task.wait_for_progress do |progress| + yield progress unless progress.nil? + end + else + task.wait_for_completion + end + + @logger.info("Finished creating snapshot #{snapshot_name} on vm #{@machine.id}") + end + end + + private + + # Enumerate VM snapshot tree + # + # This method returns an enumerator that performs a depth-first walk + # of the VM snapshot grap and yields each VirtualMachineSnapshotTree + # node. + # + # @param vm [RbVmomi::VIM::VirtualMachine] + # + # @return [Enumerator] + def enumerate_snapshots(vm) + snapshot_info = vm.snapshot + + if snapshot_info.nil? + snapshot_root = [] + else + snapshot_root = snapshot_info.rootSnapshotList + end + + recursor = lambda do |snapshot_list| + Enumerator.new do |yielder| + snapshot_list.each do |s| + # Yield the current VirtualMachineSnapshotTree object + yielder.yield s + + # Recurse into child VirtualMachineSnapshotTree objects + children = recursor.call(s.childSnapshotList) + loop do + yielder.yield children.next + end + end + end + end + + recursor.call(snapshot_root) + end + + def filter_guest_nic(vm, machine) + config = machine.provider_config + + if config.management_network_adapter_slot.nil? + return vm.guest.ipAddress + elsif config.network_adapters[config.management_network_adapter_slot].nil? || config.network_adapters[config.management_network_adapter_slot].ip_address.nil? + fail Errors::VSphereError.new, :'specified_mangement_interface_does_not_exist' unless config.management_network_adapter_slot < vm.guest.net.length + + ipAddress = nil + case config.management_network_adapter_address_family + when 'ipv4' + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv4? && addr.origin != 'linklayer' } + when 'ipv6' + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| IPAddr.new(addr.ipAddress).ipv6? && addr.origin != 'linklayer' } + else + ipAddress = vm.guest.net[config.management_network_adapter_slot].ipConfig.ipAddress.detect { |addr| addr.origin != 'linklayer' } + end + + return nil if ipAddress.nil? + return ipAddress.ipAddress + else + return config.network_adapters[config.management_network_adapter_slot].ip_address.to_s if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) + return config.network_adapters[config.management_network_adapter_slot].ip_address + end + end + + def get_datacenter(connection, machine) + connection.serviceInstance.find_datacenter(machine.provider_config.data_center_name) || fail(Errors::VSphereError, :missing_datacenter) + end + + def get_vm_by_uuid(connection, machine) + get_datacenter(connection, machine).vmFolder.findByUuid machine.id + end + + def get_resource_pool(datacenter, machine) + rp = get_compute_resource(datacenter, machine) + + resource_pool_name = machine.provider_config.resource_pool_name || '' + + entity_array = resource_pool_name.split('/') + entity_array.each do |entity_array_item| + next if entity_array_item.empty? + if rp.is_a? RbVmomi::VIM::Folder + rp = rp.childEntity.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ClusterComputeResource + rp = rp.resourcePool.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ResourcePool + rp = rp.resourcePool.find { |f| f.name == entity_array_item } || fail(Errors::VSphereError, :missing_resource_pool) + elsif rp.is_a? RbVmomi::VIM::ComputeResource + rp = rp.resourcePool.find(resource_pool_name) || fail(Errors::VSphereError, :missing_resource_pool) + else + fail Errors::VSphereError, :missing_resource_pool + end + end + rp = rp.resourcePool if !rp.is_a?(RbVmomi::VIM::ResourcePool) && rp.respond_to?(:resourcePool) + rp + end + + def get_compute_resource(datacenter, machine) + cr = find_clustercompute_or_compute_resource(datacenter, machine.provider_config.compute_resource_name) + fail Errors::VSphereError, :missing_compute_resource if cr.nil? + cr + end + + def find_clustercompute_or_compute_resource(datacenter, path) + if path.is_a? String + es = path.split('/').reject(&:empty?) + elsif path.is_a? Enumerable + es = path + else + fail "unexpected path class #{path.class}" + end + return datacenter.hostFolder if es.empty? + final = es.pop + + p = es.inject(datacenter.hostFolder) do |f, e| + f.find(e, RbVmomi::VIM::Folder) || return + end + + begin + if (x = p.find(final, RbVmomi::VIM::ComputeResource)) + x + elsif (x = p.find(final, RbVmomi::VIM::ClusterComputeResource)) + x + end + rescue Exception + # When looking for the ClusterComputeResource there seems to be some parser error in RbVmomi Folder.find, try this instead + x = p.childEntity.find { |x2| x2.name == final } + if x.is_a?(RbVmomi::VIM::ClusterComputeResource) || x.is_a?(RbVmomi::VIM::ComputeResource) + x + else + puts 'ex unknown type ' + x.to_json + nil + end + end + end + + def get_customization_spec_info_by_name(connection, machine) + name = machine.provider_config.customization_spec_name + return if name.nil? || name.empty? + + manager = connection.serviceContent.customizationSpecManager + fail Errors::VSphereError, :null_configuration_spec_manager if manager.nil? + + spec = manager.GetCustomizationSpec(name: name) + fail Errors::VSphereError, :missing_configuration_spec if spec.nil? + + spec + end + + def get_datastore(datacenter, machine) + name = machine.provider_config.data_store_name + return if name.nil? || name.empty? + + # find_datastore uses folder datastore that only lists Datastore and not StoragePod, if not found also try datastoreFolder which contains StoragePod(s) + datacenter.find_datastore(name) || datacenter.datastoreFolder.traverse(name) || fail(Errors::VSphereError, :missing_datastore) + end + + def get_network_by_name(dc, name) + base = dc.networkFolder + entity_array = name.split('/').reject(&:empty?) + entity_array.each do |item| + case base + when RbVmomi::VIM::Folder + base = base.find(item) + when RbVmomi::VIM::VmwareDistributedVirtualSwitch + idx = base.summary.portgroupName.find_index(item) + base = idx.nil? ? nil : base.portgroup[idx] + end + end + + fail(Errors::VSphereError, :missing_vlan) if base.nil? + + base + end + + #Cloning + def get_customization_spec(machine, spec_info) + customization_spec = spec_info.spec.clone + + # find all the configured private networks + private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } + return customization_spec if private_networks.nil? + + # make sure we have enough NIC settings to override with the private network settings + fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length + + # assign the private network IP to the NIC + private_networks.each_index do |idx| + customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] + end + + customization_spec + end + + def get_location(datastore, dc, machine, template) + if machine.provider_config.linked_clone + # The API for linked clones is quite strange. We can't create a linked + # straight from any VM. The disks of the VM for which we can create a + # linked clone need to be read-only and thus VC demands that the VM we + # are cloning from uses delta-disks. Only then it will allow us to + # share the base disk. + # + # Thus, this code first create a delta disk on top of the base disk for + # the to-be-cloned VM, if delta disks aren't used already. + disks = template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk) + disks.select { |disk| disk.backing.parent.nil? }.each do |disk| + spec = { + deviceChange: [ + { + operation: :remove, + device: disk + }, + { + operation: :add, + fileOperation: :create, + device: disk.dup.tap do |new_disk| + new_disk.backing = new_disk.backing.dup + new_disk.backing.fileName = "[#{disk.backing.datastore.name}]" + new_disk.backing.parent = disk.backing + end + } + ] + } + template.ReconfigVM_Task(spec: spec).wait_for_completion + end + + location = RbVmomi::VIM.VirtualMachineRelocateSpec(diskMoveType: :moveChildMostDiskBacking) + elsif datastore.is_a? RbVmomi::VIM::StoragePod + location = RbVmomi::VIM.VirtualMachineRelocateSpec + else + location = RbVmomi::VIM.VirtualMachineRelocateSpec + + location[:datastore] = datastore unless datastore.nil? + end + location[:pool] = get_resource_pool(dc, machine) unless machine.provider_config.clone_from_vm + location + end + + def get_name(machine, config, root_path) + return config.name unless config.name.nil? + + prefix = "#{root_path.basename}_#{machine.name}" + prefix.gsub!(/[^-a-z0-9_\.]/i, '') + # milliseconds + random number suffix to allow for simultaneous `vagrant up` of the same box in different dirs + prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100_000)}" + end + + def get_vm_base_folder(dc, template, config) + if config.vm_base_path.nil? + template.parent + else + dc.vmFolder.traverse(config.vm_base_path, RbVmomi::VIM::Folder, true) + end + end + + def configure_serial_ports(spec, dc, template, config) + #Enumerate serial ports + spec[:config][:deviceChange] ||= [] + + current_ports = Hash.new + + template.config.hardware.device.grep(RbVmomi::VIM::VirtualSerialPort).each_with_index { |item, index| + current_ports[index] = item + } + + puts "config.serial_ports=#{config.serial_ports.inspect}" + + current_ports_length = current_ports.length + + config_serial_ports_length = -1 + config.serial_ports.each_with_index { |item, index| + if index > config_serial_ports_length + config_serial_ports_length = index + end + } + config_serial_ports_length += 1 + + #remove unused serial ports + if config.destroy_unused_serial_ports + if current_ports_length-1 > config_serial_ports_length-1 + for index in (current_ports_length-1).downto(config_serial_ports_length-1+1) + port = current_ports[index] + + remove_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), + :device => port + } + + spec[:config][:deviceChange].push remove_port + spec[:config][:deviceChange].uniq! + end + end + end + + #we have 5 ports but want 8 ports + #add 3 ports + #edit first 5 ports + number_of_existing_ports = current_ports_length + if current_ports_length > config_serial_ports_length + #we have 5 ports but want 3 ports + #remove 2 ports + #edit first 3 ports + number_of_existing_ports = config_serial_ports_length + end + + #edit existing network interfaces + if (number_of_existing_ports > 0) + for index in (0).upto(number_of_existing_ports) + port_configuration = config.serial_ports[index] + puts "port_configuration[#{index}]=#{port_configuration.inspect}" + + #there may be no configuration for this port so dont change it, if this is the case + if !port_configuration.nil? + port = current_ports[index] + port = configure_serial_port(dc, port_configuration, port) + + edit_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => port + } + + spec[:config][:deviceChange].push edit_port + spec[:config][:deviceChange].uniq! + end + end + end + + #add extra network interfaces + for index in (number_of_existing_ports).upto(config_serial_ports_length-1) + port_configuration = config.serial_ports[index] + adapter = RbVmomi::VIM::VirtualSerialPort( + :key => index, + :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() + ) + + adapter = configure_serial_port(dc, port_configuration, adapter) + + add_port = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), + :device => adapter + } + + spec[:config][:deviceChange].push add_port + spec[:config][:deviceChange].uniq! + end + + puts "spec[:config] = #{spec[:config].inspect}" + + spec + end + + def configure_serial_port(dc, port_configuration, port) + port.yieldOnPoll = port_configuration.yield_on_poll unless port_configuration.yield_on_poll.nil? + port.connectable.connected = port_configuration.connected unless port_configuration.connected.nil? + port.connectable.startConnected = port_configuration.start_connected unless port_configuration.start_connected.nil? + + case port_configuration.backing + when 'uri' + port.backing = RbVmomi::VIM::VirtualSerialPortURIBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortURIBackingInfo) + port.backing.direction = port_configuration.direction unless port_configuration.direction.nil? + port.backing.proxyURI = port_configuration.proxy_uri unless port_configuration.proxy_uri.nil? + port.backing.serviceURI = port_configuration.service_uri unless port_configuration.service_uri.nil? + when 'pipe' + port.backing = RbVmomi::VIM::VirtualSerialPortPipeBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortPipeBackingInfo) + port.backing.endpoint = port_configuration.endpoint unless port_configuration.endpoint.nil? + port.backing.noRxLoss = port_configuration.no_rx_loss unless port_configuration.no_rx_loss.nil? + when 'file' + port.backing = RbVmomi::VIM::VirtualSerialPortFileBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortFileBackingInfo) + port.backing.fileName = port_configuration.file_name unless port_configuration.file_name.nil? + when 'device' + port.backing = RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo() if port.backing.nil? || !port.backing.is_a?(RbVmomi::VIM::VirtualSerialPortDeviceBackingInfo) + port.backing.deviceName = port_configuration.device_name unless port_configuration.device_name.nil? + port.backing.useAutoDetect = port_configuration.use_auto_detect unless port_configuration.use_auto_detect.nil? + end + + port + end + + def configure_network_cards(spec, dc, template, config) + #Enumerate lan cards + spec[:config][:deviceChange] ||= [] + + current_adapters = Hash.new + + template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| + current_adapters[index] = item + } + + puts "config.network_adapters=#{config.network_adapters.inspect}" + + current_adapters_length = current_adapters.length + + # configuration may have gaps in it, so configuration may look like this: + # vsphere.network_adapter 0, vlan: "vlan0" + # vsphere.network_adapter 1, vlan: "vlan1" + # vsphere.network_adapter 9, vlan: "vlan9" + config_network_adapters_length = -1 + config.network_adapters.each_with_index { |item, index| + if index > config_network_adapters_length + config_network_adapters_length = index + end + } + config_network_adapters_length += 1 + + #remove unused network interfaces + if config.destroy_unused_network_interfaces + if current_adapters_length-1 > config_network_adapters_length-1 + for index in (current_adapters_length-1).downto(config_network_adapters_length-1+1) + adapter = current_adapters[index] + + remove_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('remove'), + :device => adapter + } + + spec[:config][:deviceChange].push remove_adaptor + spec[:config][:deviceChange].uniq! + end + end + end + + #we have 5 cards but want 8 cards + #add 3 cards + #edit first 5 cards + number_of_existing_adapters = current_adapters_length + if current_adapters_length > config_network_adapters_length + #we have 5 cards but want 3 cards + #remove 2 cards + #edit first 3 cards + number_of_existing_adapters = config_network_adapters_length + end + + #edit existing network interfaces + if (number_of_existing_adapters > 0) + for index in (0).upto(number_of_existing_adapters) + adapter_configuration = config.network_adapters[index] + puts "adapter_configuration[#{index}]=#{adapter_configuration.inspect}" + + #there may be no configuration for this card so dont change it, if this is the case + if !adapter_configuration.nil? + adapter = current_adapters[index] + + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? + + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) + + edit_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => adapter + } + + spec[:config][:deviceChange].push edit_adaptor + spec[:config][:deviceChange].uniq! + end + end + end + + #add extra network interfaces + for index in (number_of_existing_adapters).upto(config_network_adapters_length-1) + adapter_configuration = config.network_adapters[index] + adapter = RbVmomi::VIM::VirtualVmxnet3( + :key => index, + :deviceInfo => RbVmomi::VIM::Description(), + :connectable => RbVmomi::VIM::VirtualDeviceConnectInfo() + ) + + label = "Ethernet #{index+1}" + summary = nil + summary = adapter_configuration.vlan.split('/').last unless adapter_configuration.vlan.nil? + + adapter = configure_network_card(dc, adapter_configuration, adapter, label, summary) + + add_adaptor = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('add'), + :device => adapter + } + + spec[:config][:deviceChange].push add_adaptor + spec[:config][:deviceChange].uniq! + end + + puts "spec[:config] = #{spec[:config].inspect}" + + spec + end + + def configure_network_card(dc, adapter_configuration, adapter, label, summary) + if !adapter_configuration.vlan.nil? + network = get_network_by_name(dc, adapter_configuration.vlan) + + if network.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) + switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(switchUuid: network.config.distributedVirtualSwitch.uuid, portgroupKey: network.key) + adapter.backing = RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(port: switch_port) + else + # not connected to a distibuted switch? + adapter.backing = RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo(network: network, deviceName: network.name) + end + end + + adapter.deviceInfo.label = label unless label.nil? + adapter.deviceInfo.summary = summary unless summary.nil? + + adapter.connectable.allowGuestControl = adapter_configuration.allow_guest_control unless adapter_configuration.allow_guest_control.nil? + adapter.connectable.connected = adapter_configuration.connected unless adapter_configuration.connected.nil? + adapter.connectable.startConnected = adapter_configuration.start_connected unless adapter_configuration.start_connected.nil? + + adapter.addressType = adapter_configuration.address_type unless adapter_configuration.address_type.nil? + adapter.macAddress = adapter_configuration.mac_address unless adapter_configuration.mac_address.nil? + adapter.wakeOnLanEnabled = adapter_configuration.wake_on_lan_enabled unless adapter_configuration.wake_on_lan_enabled.nil? + + adapter + end + + def add_custom_memory(spec, memory_mb) + spec[:config][:memoryMB] = Integer(memory_mb) + end + + def add_custom_cpu(spec, cpu_count) + spec[:config][:numCPUs] = Integer(cpu_count) + end + + def add_custom_cpu_reservation(spec, cpu_reservation) + spec[:config][:cpuAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: cpu_reservation) + end + + def add_custom_mem_reservation(spec, mem_reservation) + spec[:config][:memoryAllocation] = RbVmomi::VIM.ResourceAllocationInfo(reservation: mem_reservation) + end + + def add_custom_extra_config(spec, extra_config = {}) + return if extra_config.empty? + + # extraConfig must be an array of hashes with `key` and `value` + # entries. + spec[:config][:extraConfig] = extra_config.map { |k, v| { 'key' => k, 'value' => v } } + end + + def add_custom_notes(spec, notes) + spec[:config][:annotation] = notes + end + end + end +end From 64957699f3822cfb9496fec55d1f5efd6753a4e2 Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Thu, 17 Nov 2016 17:52:50 -0700 Subject: [PATCH 29/39] Rename unused func/block args with leading _ --- lib/vSphere/driver.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 67b7fb43..d41b7fdb 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -643,7 +643,7 @@ def configure_serial_ports(spec, dc, template, config) current_ports_length = current_ports.length config_serial_ports_length = -1 - config.serial_ports.each_with_index { |item, index| + config.serial_ports.each_with_index { |_item, index| if index > config_serial_ports_length config_serial_ports_length = index end @@ -724,7 +724,7 @@ def configure_serial_ports(spec, dc, template, config) spec end - def configure_serial_port(dc, port_configuration, port) + def configure_serial_port(_dc, port_configuration, port) port.yieldOnPoll = port_configuration.yield_on_poll unless port_configuration.yield_on_poll.nil? port.connectable.connected = port_configuration.connected unless port_configuration.connected.nil? port.connectable.startConnected = port_configuration.start_connected unless port_configuration.start_connected.nil? @@ -770,7 +770,7 @@ def configure_network_cards(spec, dc, template, config) # vsphere.network_adapter 1, vlan: "vlan1" # vsphere.network_adapter 9, vlan: "vlan9" config_network_adapters_length = -1 - config.network_adapters.each_with_index { |item, index| + config.network_adapters.each_with_index { |_item, index| if index > config_network_adapters_length config_network_adapters_length = index end From a30735c58fe0a633120b1e21efc775b45994c8ac Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Thu, 17 Nov 2016 17:56:06 -0700 Subject: [PATCH 30/39] Fix setting a class property variable --- lib/vSphere/config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 435f5a0f..eb5b7548 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -136,7 +136,7 @@ def initialize(serial_port_config) attr_reader :custom_attributes def initialize - wait_for_customization_timeout = 600 + @wait_for_customization_timeout = 600 @destroy_unused_network_interfaces = UNSET_VALUE @destroy_unused_serial_ports = UNSET_VALUE @network_adapters = {} From 9aa14e7cfbc4ebad16596d5f5428fd99071ed288 Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Thu, 17 Nov 2016 18:08:09 -0700 Subject: [PATCH 31/39] Run rubocop with --auto-correct Modifies many files except for driver.rb; rubocop seems to stall out when auto-correcting driver.rb --- lib/vSphere/action.rb | 1 - lib/vSphere/action/destroy.rb | 18 ++-- lib/vSphere/action/snapshot_delete.rb | 2 +- lib/vSphere/action/snapshot_restore.rb | 2 +- lib/vSphere/action/snapshot_save.rb | 4 +- lib/vSphere/config.rb | 127 ++++++++++++------------- lib/vSphere/driver.rb | 106 ++++++++++----------- lib/vSphere/provider.rb | 4 +- 8 files changed, 130 insertions(+), 134 deletions(-) diff --git a/lib/vSphere/action.rb b/lib/vSphere/action.rb index 2c8367f6..0e2aa543 100644 --- a/lib/vSphere/action.rb +++ b/lib/vSphere/action.rb @@ -150,7 +150,6 @@ def self.action_reload end end - # TODO: Remove the if guard when Vagrant 1.8.0 is the minimum version. # rubocop:disable IndentationWidth if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') diff --git a/lib/vSphere/action/destroy.rb b/lib/vSphere/action/destroy.rb index 7c3102e5..56a9222b 100644 --- a/lib/vSphere/action/destroy.rb +++ b/lib/vSphere/action/destroy.rb @@ -19,18 +19,16 @@ def call(env) private def destroy_vm(env) - begin - env[:ui].info I18n.t('vsphere.destroy_vm') + env[:ui].info I18n.t('vsphere.destroy_vm') - env[:machine].provider.driver.destroy do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) - end - rescue Errors::VSphereError - raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message + env[:machine].provider.driver.destroy do |progress| + env[:ui].clear_line + env[:ui].report_progress(progress, 100, false) end + rescue Errors::VSphereError + raise + rescue StandardError => e + raise Errors::VSphereError.new, e.message end end end diff --git a/lib/vSphere/action/snapshot_delete.rb b/lib/vSphere/action/snapshot_delete.rb index 8fde5cf7..2a9b58aa 100644 --- a/lib/vSphere/action/snapshot_delete.rb +++ b/lib/vSphere/action/snapshot_delete.rb @@ -8,7 +8,7 @@ def initialize(app, _env) def call(env) snapshot_name = env[:snapshot_name] - + env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.deleting", name: snapshot_name)) env[:machine].provider.driver.delete_snapshot(snapshot_name) do |progress| diff --git a/lib/vSphere/action/snapshot_restore.rb b/lib/vSphere/action/snapshot_restore.rb index 276f322f..a5016993 100644 --- a/lib/vSphere/action/snapshot_restore.rb +++ b/lib/vSphere/action/snapshot_restore.rb @@ -17,7 +17,7 @@ def call(env) end env[:ui].clear_line - #env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.restored", name: snapshot_name)) + # env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.restored", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/action/snapshot_save.rb b/lib/vSphere/action/snapshot_save.rb index ea428c24..558134e0 100644 --- a/lib/vSphere/action/snapshot_save.rb +++ b/lib/vSphere/action/snapshot_save.rb @@ -10,7 +10,7 @@ def call(env) snapshot_name = env[:snapshot_name] env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.saving", name: snapshot_name)) - + env[:machine].provider.driver.create_snapshot(snapshot_name) do |progress| env[:ui].clear_line env[:ui].report_progress(progress, 100, false) @@ -18,7 +18,7 @@ def call(env) env[:ui].clear_line env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.saved", name: snapshot_name)) - + @app.call env end end diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index eb5b7548..812fd7ef 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -3,7 +3,6 @@ module VagrantPlugins module VSphere class Config < Vagrant.plugin('2', :config) - class NetworkConfiguration attr_accessor :allow_guest_control attr_accessor :connected @@ -39,67 +38,67 @@ def initialize(network_config) end class SerialPortConfiguration - attr_accessor :yield_on_poll - attr_accessor :connected - attr_accessor :start_connected - attr_accessor :backing - - attr_accessor :direction - attr_accessor :proxy_uri - attr_accessor :service_uri - - attr_accessor :endpoint - attr_accessor :no_rx_loss - - attr_accessor :file_name - - attr_accessor :device_name - attr_accessor :use_auto_detect - - def initialize(serial_port_config) - @yield_on_poll = true - @connected = true - @start_connected = true - @backing = '' - - @direction = '' - @proxy_uri = '' - @service_uri = '' - - @endpoint = '' - @no_rx_loss = true - - @file_name = '' - - @device_name = '' - @use_auto_detect = false - - @yield_on_poll = serial_port_config[:yield_on_poll] if serial_port_config.key?(:yield_on_poll) - @connected = network_config[:connected] if network_config.key?(:connected) - @start_connected = network_config[:start_connected] if network_config.key?(:start_connected) - @backing = serial_port_config[:backing] if serial_port_config.key?(:backing) - if !(@backing == 'uri' || @backing == 'pipe' || @backing == 'file' || @backing == 'device') - raise "The only valid values allowed for backing are 'uri', 'pipe', 'file', 'device'" - end - - @direction = serial_port_config[:direction] if serial_port_config.key?(:direction) - if @backing == 'uri' && !(@direction == 'client' || @direction == 'server') - raise "The only valid values allowed for direction are 'client', 'server'" - end - @proxy_uri = serial_port_config[:proxy_uri] if serial_port_config.key?(:proxy_uri) - @service_uri = serial_port_config[:service_uri] if serial_port_config.key?(:service_uri) - - @endpoint = serial_port_config[:endpoint] if serial_port_config.key?(:endpoint) - if @backing == 'pipe' && !(@endpoint == 'client' || @endpoint == 'server') - raise "The only valid values allowed for endpoint are 'client', 'server'" - end - @no_rx_loss = serial_port_config[:no_rx_loss] if serial_port_config.key?(:no_rx_loss) - - @file_name = serial_port_config[:file_name] if serial_port_config.key?(:file_name) - - @device_name = serial_port_config[:device_name] if serial_port_config.key?(:device_name) - @use_auto_detect = serial_port_config[:use_auto_detect] if serial_port_config.key?(:use_auto_detect) + attr_accessor :yield_on_poll + attr_accessor :connected + attr_accessor :start_connected + attr_accessor :backing + + attr_accessor :direction + attr_accessor :proxy_uri + attr_accessor :service_uri + + attr_accessor :endpoint + attr_accessor :no_rx_loss + + attr_accessor :file_name + + attr_accessor :device_name + attr_accessor :use_auto_detect + + def initialize(serial_port_config) + @yield_on_poll = true + @connected = true + @start_connected = true + @backing = '' + + @direction = '' + @proxy_uri = '' + @service_uri = '' + + @endpoint = '' + @no_rx_loss = true + + @file_name = '' + + @device_name = '' + @use_auto_detect = false + + @yield_on_poll = serial_port_config[:yield_on_poll] if serial_port_config.key?(:yield_on_poll) + @connected = network_config[:connected] if network_config.key?(:connected) + @start_connected = network_config[:start_connected] if network_config.key?(:start_connected) + @backing = serial_port_config[:backing] if serial_port_config.key?(:backing) + unless @backing == 'uri' || @backing == 'pipe' || @backing == 'file' || @backing == 'device' + fail "The only valid values allowed for backing are 'uri', 'pipe', 'file', 'device'" + end + + @direction = serial_port_config[:direction] if serial_port_config.key?(:direction) + if @backing == 'uri' && !(@direction == 'client' || @direction == 'server') + fail "The only valid values allowed for direction are 'client', 'server'" + end + @proxy_uri = serial_port_config[:proxy_uri] if serial_port_config.key?(:proxy_uri) + @service_uri = serial_port_config[:service_uri] if serial_port_config.key?(:service_uri) + + @endpoint = serial_port_config[:endpoint] if serial_port_config.key?(:endpoint) + if @backing == 'pipe' && !(@endpoint == 'client' || @endpoint == 'server') + fail "The only valid values allowed for endpoint are 'client', 'server'" end + @no_rx_loss = serial_port_config[:no_rx_loss] if serial_port_config.key?(:no_rx_loss) + + @file_name = serial_port_config[:file_name] if serial_port_config.key?(:file_name) + + @device_name = serial_port_config[:device_name] if serial_port_config.key?(:device_name) + @use_auto_detect = serial_port_config[:use_auto_detect] if serial_port_config.key?(:use_auto_detect) + end end attr_accessor :host @@ -131,9 +130,9 @@ def initialize(serial_port_config) attr_accessor :destroy_unused_serial_ports attr_accessor :management_network_adapter_slot attr_accessor :management_network_adapter_address_family - attr_reader :network_adapters - attr_reader :serial_ports - attr_reader :custom_attributes + attr_reader :network_adapters + attr_reader :serial_ports + attr_reader :custom_attributes def initialize @wait_for_customization_timeout = 600 diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index d41b7fdb..89447dea 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -20,15 +20,15 @@ def initialize(machine) end def connection - raise "connection be called from a code block!" if !block_given? + fail "connection be called from a code block!" unless block_given? begin config = @machine.provider_config current_connection = RbVmomi::VIM.connect host: config.host, - user: config.user, password: config.password, - insecure: config.insecure, proxyHost: config.proxy_host, - proxyPort: config.proxy_port + user: config.user, password: config.password, + insecure: config.insecure, proxyHost: config.proxy_host, + proxyPort: config.proxy_port yield current_connection rescue @@ -48,8 +48,8 @@ def ssh_info ip_address = filter_guest_nic(vm, @machine) return nil if ip_address.nil? || ip_address.empty? { - host: ip_address, - port: 22 + host: ip_address, + port: 22 } end end @@ -225,7 +225,7 @@ def clone(root_path) sleep_time = 5 while wait - events = vem.QueryEvents(filter:RbVmomi::VIM::EventFilterSpec(entity:RbVmomi::VIM::EventFilterSpecByEntity(entity: new_vm, recursion:RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) + events = vem.QueryEvents(filter: RbVmomi::VIM::EventFilterSpec(entity: RbVmomi::VIM::EventFilterSpecByEntity(entity: new_vm, recursion: RbVmomi::VIM::EventFilterSpecRecursionOption(:self)), eventTypeId: ['CustomizationSucceeded'])) if events.size > 0 events.each do |e| @@ -242,8 +242,8 @@ def clone(root_path) end rescue Errors::VSphereError raise - #rescue StandardError => e - # raise Errors::VSphereError.new, e.message + # rescue StandardError => e + # raise Errors::VSphereError.new, e.message end # TODO: handle interrupted status in the environment, should the vm be destroyed? @@ -431,7 +431,7 @@ def filter_guest_nic(vm, machine) end return nil if ipAddress.nil? - return ipAddress.ipAddress + return ipAddress.ipAddress else return config.network_adapters[config.management_network_adapter_slot].ip_address.to_s if config.network_adapters[config.management_network_adapter_slot].ip_address.is_a?(IPAddr) return config.network_adapters[config.management_network_adapter_slot].ip_address @@ -535,11 +535,11 @@ def get_network_by_name(dc, name) entity_array = name.split('/').reject(&:empty?) entity_array.each do |item| case base - when RbVmomi::VIM::Folder - base = base.find(item) - when RbVmomi::VIM::VmwareDistributedVirtualSwitch - idx = base.summary.portgroupName.find_index(item) - base = idx.nil? ? nil : base.portgroup[idx] + when RbVmomi::VIM::Folder + base = base.find(item) + when RbVmomi::VIM::VmwareDistributedVirtualSwitch + idx = base.summary.portgroupName.find_index(item) + base = idx.nil? ? nil : base.portgroup[idx] end end @@ -548,7 +548,7 @@ def get_network_by_name(dc, name) base end - #Cloning + # Cloning def get_customization_spec(machine, spec_info) customization_spec = spec_info.spec.clone @@ -629,28 +629,28 @@ def get_vm_base_folder(dc, template, config) end def configure_serial_ports(spec, dc, template, config) - #Enumerate serial ports + # Enumerate serial ports spec[:config][:deviceChange] ||= [] current_ports = Hash.new - template.config.hardware.device.grep(RbVmomi::VIM::VirtualSerialPort).each_with_index { |item, index| + template.config.hardware.device.grep(RbVmomi::VIM::VirtualSerialPort).each_with_index do |item, index| current_ports[index] = item - } + end puts "config.serial_ports=#{config.serial_ports.inspect}" current_ports_length = current_ports.length config_serial_ports_length = -1 - config.serial_ports.each_with_index { |_item, index| + config.serial_ports.each_with_index do |_item, index| if index > config_serial_ports_length config_serial_ports_length = index end - } + end config_serial_ports_length += 1 - #remove unused serial ports + # remove unused serial ports if config.destroy_unused_serial_ports if current_ports_length-1 > config_serial_ports_length-1 for index in (current_ports_length-1).downto(config_serial_ports_length-1+1) @@ -667,25 +667,25 @@ def configure_serial_ports(spec, dc, template, config) end end - #we have 5 ports but want 8 ports - #add 3 ports - #edit first 5 ports + # we have 5 ports but want 8 ports + # add 3 ports + # edit first 5 ports number_of_existing_ports = current_ports_length if current_ports_length > config_serial_ports_length - #we have 5 ports but want 3 ports - #remove 2 ports - #edit first 3 ports + # we have 5 ports but want 3 ports + # remove 2 ports + # edit first 3 ports number_of_existing_ports = config_serial_ports_length end - #edit existing network interfaces - if (number_of_existing_ports > 0) + # edit existing network interfaces + if number_of_existing_ports > 0 for index in (0).upto(number_of_existing_ports) port_configuration = config.serial_ports[index] puts "port_configuration[#{index}]=#{port_configuration.inspect}" - #there may be no configuration for this port so dont change it, if this is the case - if !port_configuration.nil? + # there may be no configuration for this port so dont change it, if this is the case + unless port_configuration.nil? port = current_ports[index] port = configure_serial_port(dc, port_configuration, port) @@ -700,7 +700,7 @@ def configure_serial_ports(spec, dc, template, config) end end - #add extra network interfaces + # add extra network interfaces for index in (number_of_existing_ports).upto(config_serial_ports_length-1) port_configuration = config.serial_ports[index] adapter = RbVmomi::VIM::VirtualSerialPort( @@ -752,14 +752,14 @@ def configure_serial_port(_dc, port_configuration, port) end def configure_network_cards(spec, dc, template, config) - #Enumerate lan cards + # Enumerate lan cards spec[:config][:deviceChange] ||= [] current_adapters = Hash.new - template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index { |item, index| + template.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).each_with_index do |item, index| current_adapters[index] = item - } + end puts "config.network_adapters=#{config.network_adapters.inspect}" @@ -767,17 +767,17 @@ def configure_network_cards(spec, dc, template, config) # configuration may have gaps in it, so configuration may look like this: # vsphere.network_adapter 0, vlan: "vlan0" - # vsphere.network_adapter 1, vlan: "vlan1" - # vsphere.network_adapter 9, vlan: "vlan9" + # vsphere.network_adapter 1, vlan: "vlan1" + # vsphere.network_adapter 9, vlan: "vlan9" config_network_adapters_length = -1 - config.network_adapters.each_with_index { |_item, index| + config.network_adapters.each_with_index do |_item, index| if index > config_network_adapters_length config_network_adapters_length = index end - } + end config_network_adapters_length += 1 - #remove unused network interfaces + # remove unused network interfaces if config.destroy_unused_network_interfaces if current_adapters_length-1 > config_network_adapters_length-1 for index in (current_adapters_length-1).downto(config_network_adapters_length-1+1) @@ -794,25 +794,25 @@ def configure_network_cards(spec, dc, template, config) end end - #we have 5 cards but want 8 cards - #add 3 cards - #edit first 5 cards + # we have 5 cards but want 8 cards + # add 3 cards + # edit first 5 cards number_of_existing_adapters = current_adapters_length if current_adapters_length > config_network_adapters_length - #we have 5 cards but want 3 cards - #remove 2 cards - #edit first 3 cards + # we have 5 cards but want 3 cards + # remove 2 cards + # edit first 3 cards number_of_existing_adapters = config_network_adapters_length end - #edit existing network interfaces - if (number_of_existing_adapters > 0) + # edit existing network interfaces + if number_of_existing_adapters > 0 for index in (0).upto(number_of_existing_adapters) adapter_configuration = config.network_adapters[index] puts "adapter_configuration[#{index}]=#{adapter_configuration.inspect}" - #there may be no configuration for this card so dont change it, if this is the case - if !adapter_configuration.nil? + # there may be no configuration for this card so dont change it, if this is the case + unless adapter_configuration.nil? adapter = current_adapters[index] label = "Ethernet #{index+1}" @@ -832,7 +832,7 @@ def configure_network_cards(spec, dc, template, config) end end - #add extra network interfaces + # add extra network interfaces for index in (number_of_existing_adapters).upto(config_network_adapters_length-1) adapter_configuration = config.network_adapters[index] adapter = RbVmomi::VIM::VirtualVmxnet3( @@ -862,7 +862,7 @@ def configure_network_cards(spec, dc, template, config) end def configure_network_card(dc, adapter_configuration, adapter, label, summary) - if !adapter_configuration.vlan.nil? + unless adapter_configuration.vlan.nil? network = get_network_by_name(dc, adapter_configuration.vlan) if network.is_a?(RbVmomi::VIM::DistributedVirtualPortgroup) diff --git a/lib/vSphere/provider.rb b/lib/vSphere/provider.rb index 1d05beff..ccc0fa63 100644 --- a/lib/vSphere/provider.rb +++ b/lib/vSphere/provider.rb @@ -10,7 +10,7 @@ class Provider < Vagrant.plugin('2', :provider) def initialize(machine) @logger = Log4r::Logger.new('vagrant::provider::vsphere') @machine = machine - + # This method will load in our driver, so we call it now to # initialize it. machine_id_changed @@ -62,4 +62,4 @@ def to_s end end end -end \ No newline at end of file +end From 4cc23a140d5e27bfa5957d06a05949cb87b6cb4f Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Wed, 23 Nov 2016 08:48:18 -0700 Subject: [PATCH 32/39] Auto-generate a new rubocop config --- .rubocop_todo.yml | 48 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 2f92dbab..4af148b9 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,5 +1,5 @@ # This configuration was generated by `rubocop --auto-gen-config` -# on 2016-03-08 12:12:34 -0700 using RuboCop version 0.32.1. +# on 2016-11-23 08:47:01 -0700 using RuboCop version 0.32.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -9,52 +9,84 @@ Lint/NonLocalExitFromIterator: Enabled: false -# Offense count: 24 +# Offense count: 1 +Lint/RescueException: + Enabled: false + +# Offense count: 1 +Metrics/BlockNesting: + Max: 4 + +# Offense count: 1 +Style/AccessorMethodName: + Enabled: false + +# Offense count: 26 Style/Documentation: Enabled: false -# Offense count: 3 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/EmptyLinesAroundBlockBody: Enabled: false -# Offense count: 1 +# Offense count: 3 # Cop supports --auto-correct. Style/EmptyLiteral: Enabled: false -# Offense count: 1 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/FirstParameterIndentation: Enabled: false # Offense count: 6 +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/For: + Enabled: false + +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues. Style/HashSyntax: Enabled: false +# Offense count: 2 +# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. +Style/Next: + Enabled: false + +# Offense count: 2 +# Configuration parameters: NamePrefix, NamePrefixBlacklist. +Style/PredicateName: + Enabled: false + # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. Style/RegexpLiteral: Enabled: false -# Offense count: 3 +# Offense count: 17 # Cop supports --auto-correct. # Configuration parameters: MultiSpaceAllowedForOperators. Style/SpaceAroundOperators: Enabled: false -# Offense count: 1 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: Enabled: false -# Offense count: 4 +# Offense count: 6 # Cop supports --auto-correct. Style/SymbolLiteral: Enabled: false + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, SupportedStyles. +Style/VariableName: + Enabled: false From 18993b675f06069afe912936db9855c10597ec6d Mon Sep 17 00:00:00 2001 From: Michael Brandt Date: Wed, 23 Nov 2016 08:51:48 -0700 Subject: [PATCH 33/39] Delete spec files for deleted source files --- spec/connect_vsphere_spec.rb | 26 ------- spec/get_ssh_info_spec.rb | 131 ----------------------------------- spec/get_state_spec.rb | 55 --------------- spec/spec_helper.rb | 3 - 4 files changed, 215 deletions(-) delete mode 100644 spec/connect_vsphere_spec.rb delete mode 100644 spec/get_ssh_info_spec.rb delete mode 100644 spec/get_state_spec.rb diff --git a/spec/connect_vsphere_spec.rb b/spec/connect_vsphere_spec.rb deleted file mode 100644 index 1eea83da..00000000 --- a/spec/connect_vsphere_spec.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'spec_helper' - -describe VagrantPlugins::VSphere::Action::ConnectVSphere do - before :each do - described_class.new(@app, @env).call(@env) - end - - it 'should connect to vSphere' do - expect(VIM).to have_received(:connect).with( - host: @env[:machine].provider_config.host, - user: @env[:machine].provider_config.user, - password: @env[:machine].provider_config.password, - insecure: @env[:machine].provider_config.insecure, - proxyHost: @env[:machine].provider_config.proxy_host, - proxyPort: @env[:machine].provider_config.proxy_port - ) - end - - it 'should add the vSphere connection to the environment' do - expect(@env[:vSphere_connection]).to be @vim - end - - it 'should call the next item in the middleware stack' do - expect(@app).to have_received :call - end -end diff --git a/spec/get_ssh_info_spec.rb b/spec/get_ssh_info_spec.rb deleted file mode 100644 index f522c939..00000000 --- a/spec/get_ssh_info_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'spec_helper' - -describe VagrantPlugins::VSphere::Action::GetSshInfo do - before :each do - @env[:vSphere_connection] = @vim - end - - it 'should set the ssh info to nil if machine ID is not set' do - call - - expect(@env.key?(:machine_ssh_info)).to be true - expect(@env[:machine_ssh_info]).to be nil - end - - it 'should set the ssh info to nil for a VM that does not exist' do - @env[:machine].stub(:id).and_return(MISSING_UUID) - - call - - expect(@env.key?(:machine_ssh_info)).to be true - expect(@env[:machine_ssh_info]).to be nil - end - - it 'should set the ssh info host to the IP an existing VM' do - @env[:machine].stub(:id).and_return(EXISTING_UUID) - call - - expect(@env[:machine_ssh_info][:host]).to be IP_ADDRESS - end - - context 'when acting on a VM with a single network adapter' do - before do - allow(@vm.guest).to receive(:ipAddress) { '127.0.0.2' } - @env[:machine].stub(:id).and_return(EXISTING_UUID) - end - - it 'should return the correct ip address' do - call - expect(@env[:machine_ssh_info][:host]).to eq '127.0.0.2' - end - end - - context 'when acting on a VM with multiple network adapters' do - before do - @env[:machine].stub(:id).and_return(EXISTING_UUID) - allow(@vm.guest).to receive(:net) { - [double('GuestNicInfo', - ipAddress: ['bad address', '127.0.0.1'], - deviceConfigId: 4000, - ipConfig: double('NetIpConfigInfo', - ipAddress: [double('NetIpConfigInfoIpAddress', - ipAddress: 'bad address', state: 'unknown'), - double('NetIpConfigInfoIpAddress', - ipAddress: '127.0.0.1', state: 'preferred')] - ) - ), - double('GuestNicInfo', - ipAddress: ['bad address', '255.255.255.255'], - deviceConfigId: -1, - ipConfig: double('NetIpConfigInfo', - ipAddress: [double('NetIpConfigInfoIpAddress', - ipAddress: 'bad address', state: 'unknown'), - double('NetIpConfigInfoIpAddress', - ipAddress: '255.255.255.255', state: 'preferred')] - ) - ) - ] - } - end - - context 'when the real_nic_ip option is false' do - it 'sets the ssh info the original adapter' do - call - expect(@env[:machine_ssh_info][:host]).to eq IP_ADDRESS - end - end - - context 'when the real_nic_ip option is true' do - before do - @env[:machine].provider_config.stub(:real_nic_ip).and_return(true) - end - context 'when there are mutiple valid adapters' do - before do - allow(@vm.guest).to receive(:net) { - [double('GuestNicInfo', - ipAddress: ['bad address', '127.0.0.1'], - deviceConfigId: 4000, - ipConfig: double('NetIpConfigInfo', - ipAddress: [double('NetIpConfigInfoIpAddress', - ipAddress: 'bad address', state: 'unknown'), - double('NetIpConfigInfoIpAddress', - ipAddress: '127.0.0.2', state: 'preferred')] - ) - ), - double('GuestNicInfo', - ipAddress: ['bad address', '255.255.255.255'], - deviceConfigId: 2000, - ipConfig: double('NetIpConfigInfo', - ipAddress: [double('NetIpConfigInfoIpAddress', - ipAddress: 'bad address', state: 'unknown'), - double('NetIpConfigInfoIpAddress', - ipAddress: '255.255.255.255', state: 'preferred')] - ) - ) - ] - } - - end - it 'should raise an error' do - expect { call }.to raise_error(VagrantPlugins::VSphere::Errors::VSphereError) - end - end - - it 'sets the ssh info host to the correct adapter' do - call - expect(@env[:machine_ssh_info][:host]).to eq IP_ADDRESS - end - - context 'when the VM networking is uninitialized' do - before do - allow(@vm.guest).to receive(:net) { [] } - allow(@vm.guest).to receive(:ipAddress) { '123.234.156.78' } - end - it 'should set the ssh info to the guest ipAddress and port 22 if no valid adapters are present' do - call - expect(@env[:machine_ssh_info]).to eq(host: '123.234.156.78', port: 22) - end - end - end - end -end diff --git a/spec/get_state_spec.rb b/spec/get_state_spec.rb deleted file mode 100644 index a54aedb1..00000000 --- a/spec/get_state_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'spec_helper' -require 'vSphere/util/vim_helpers' - -describe VagrantPlugins::VSphere::Action::GetState do - before :each do - @env[:vSphere_connection] = @vim - end - - it 'should set state id to not created if machine ID is not set' do - call - - expect(@env[:machine_state_id]).to be :not_created - end - - it 'should set state id to not created if VM is not found' do - @env[:machine].stub(:id).and_return(MISSING_UUID) - - call - - expect(@env[:machine_state_id]).to be :not_created - end - - it 'should set state id to running if machine is powered on' do - @env[:machine].stub(:id).and_return(EXISTING_UUID) - @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_ON) - - call - - expect(@env[:machine_state_id]).to be :running - end - - it 'should set state id to powered off if machine is powered off' do - @env[:machine].stub(:id).and_return(EXISTING_UUID) - @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::POWERED_OFF) - - call - - expect(@env[:machine_state_id]).to be :poweroff - end - - it 'should set state id to powered off if machine is suspended' do - @env[:machine].stub(:id).and_return(EXISTING_UUID) - @vm.runtime.stub(:powerState).and_return(VagrantPlugins::VSphere::Util::VmState::SUSPENDED) - - call - - expect(@env[:machine_state_id]).to be :poweroff - end - - it 'should call the next item in the middleware stack' do - call - - expect(@app).to have_received :call - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b9384370..44f260d1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,11 +2,8 @@ require 'pathname' require 'vSphere/errors' require 'vSphere/action' -require 'vSphere/action/connect_vsphere' require 'vSphere/action/close_vsphere' require 'vSphere/action/is_created' -require 'vSphere/action/get_state' -require 'vSphere/action/get_ssh_info' require 'vSphere/action/clone' require 'vSphere/action/message_already_created' require 'vSphere/action/message_not_created' From 9af830c890743adfa2a5ab76b36e672b2fb9f2be Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 7 Dec 2016 08:52:52 +0000 Subject: [PATCH 34/39] Display the name of vlan that is missing --- lib/vSphere/driver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 89447dea..5c149773 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -543,7 +543,7 @@ def get_network_by_name(dc, name) end end - fail(Errors::VSphereError, :missing_vlan) if base.nil? + fail(Errors::VSphereError, :missing_vlan + ' - ' + name) if base.nil? base end From d2545f1f287986dfb40e22a1b270ba67220b31ab Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 7 Dec 2016 09:17:34 +0000 Subject: [PATCH 35/39] Show vlan before throwing exception --- lib/vSphere/driver.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 5c149773..dcaddc79 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -543,7 +543,9 @@ def get_network_by_name(dc, name) end end - fail(Errors::VSphereError, :missing_vlan + ' - ' + name) if base.nil? + puts I18n.t( :missing_vlan + ' - ' + name) + + fail(Errors::VSphereError, :missing_vlan) if base.nil? base end From 22b06026a8434ab21833f15a464f7f65c1714f2d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 7 Dec 2016 09:28:08 +0000 Subject: [PATCH 36/39] The key shouldn't change for looking up localized message --- lib/vSphere/driver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index dcaddc79..94c12ccb 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -543,7 +543,7 @@ def get_network_by_name(dc, name) end end - puts I18n.t( :missing_vlan + ' - ' + name) + puts I18n.t(:missing_vlan) + ' - ' + name fail(Errors::VSphereError, :missing_vlan) if base.nil? From 0ce792719be519009ec250402d07970be7a88ae9 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 7 Dec 2016 09:34:54 +0000 Subject: [PATCH 37/39] Just output vlan --- lib/vSphere/driver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 94c12ccb..462a52a2 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -543,7 +543,7 @@ def get_network_by_name(dc, name) end end - puts I18n.t(:missing_vlan) + ' - ' + name + puts 'vlan: ' + name fail(Errors::VSphereError, :missing_vlan) if base.nil? From 178d93df52b705493ee63dcacbe4e1eee1ec4795 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 21 Jul 2017 15:28:12 +0100 Subject: [PATCH 38/39] Provide a way to configure the size of disk --- README.md | 5 +++ lib/vSphere/config.rb | 14 ++++++ lib/vSphere/driver.rb | 101 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 8d10e78d..536b0181 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ This provider has the following settings, all are required unless noted: * `destroy_unused_network_interfaces` - _Optional_ boolean - should network cards that have not been configured explicitly, be deleted. If set to false then existing network cards are left alone. * `network_adapter` - Array of network card configuration +* `disk` - Array of disk configuration * `destroy_unused_serial_ports` - _Optional_ boolean - should serial ports that have not been configured explicitly, be deleted. If set to false then existing serial ports are left alone. * `serial_port` - Array of serial port configuration @@ -172,6 +173,10 @@ This provider has the following settings, all are required unless noted: for vm and reserve ip address on DHCP server for mac address. * `wake_on_lan_enabled` - _Optional_ boolean - should vm turn on when magic packet is received on network card +## Disk configuration +* `slot` - integer - zero based array of the disk index +* `size` - integer - the size the disk should be resized to in kibibyte (1024 bytes) + ## Serial port configuration * `yield_on_poll` - _Optional_ boolean - Enables CPU yield behavior. If you set yieldOnPoll to true, the virtual machine will periodically relinquish the processor if its sole task is polling the virtual serial port. The amount of time it takes to diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 812fd7ef..6e52b867 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -37,6 +37,14 @@ def initialize(network_config) end end + class DiskConfiguration + attr_accessor :size + + def initialize(disk_config) + @size = nil + end + end + class SerialPortConfiguration attr_accessor :yield_on_poll attr_accessor :connected @@ -131,6 +139,7 @@ def initialize(serial_port_config) attr_accessor :management_network_adapter_slot attr_accessor :management_network_adapter_address_family attr_reader :network_adapters + attr_reader :disks attr_reader :serial_ports attr_reader :custom_attributes @@ -139,6 +148,7 @@ def initialize @destroy_unused_network_interfaces = UNSET_VALUE @destroy_unused_serial_ports = UNSET_VALUE @network_adapters = {} + @disks = {} @custom_attributes = {} @serial_ports = {} @extra_config = {} @@ -152,6 +162,10 @@ def network_adapter(slot, **opts) @network_adapters[slot] = NetworkConfiguration.new opts end + def disk(slot, **opts) + @disks[slot] = DiskConfiguration.new opts + end + def serial_port(slot, **opts) @serial_ports[slot] = SerialPortConfiguration.new opts end diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index 462a52a2..daa25ce1 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -126,6 +126,34 @@ def suspended? end end + def resize_disk(index, new_capacity_in_kb) + return nil if @machine.id.nil? + config = machine.provider_config + connection do |conn| + vm = get_vm_by_uuid conn, @machine + + current_disks = Hash.new + vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk).each_with_index do |item, index| + current_disks[index] = item + end + + if current_disks[index].capacityInKB != new_capacity_in_kb + @logger.info("Start resizing disk for vm #{@machine.id} for disk index #{index} to size of #{new_capacity_in_kb}") + current_disks[index].capacityInKB = new_capacity_in_kb + spec = { + deviceChange: [ + { + operation: :edit, + device: current_disks[index] + } + ] + } + vm.ReconfigVM_Task(spec: spec).wait_for_completion + @logger.info("Finished resizing disk for vm #{@machine.id} for disk index #{index} to size of #{new_capacity_in_kb}") + end + end + end + def clone(root_path) config = machine.provider_config connection do |conn| @@ -149,6 +177,7 @@ def clone(root_path) spec[:customization] = get_customization_spec(@machine, customization_info) unless customization_info.nil? spec = configure_network_cards(spec, dc, template, config) + spec = configure_disks(spec, dc, template, config) add_custom_memory(spec, config.memory_mb) unless config.memory_mb.nil? add_custom_cpu(spec, config.cpu_count) unless config.cpu_count.nil? @@ -556,14 +585,14 @@ def get_customization_spec(machine, spec_info) # find all the configured private networks private_networks = machine.config.vm.networks.find_all { |n| n[0].eql? :private_network } - return customization_spec if private_networks.nil? - - # make sure we have enough NIC settings to override with the private network settings - fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length + if !private_networks.nil? + # make sure we have enough NIC settings to override with the private network settings + fail Errors::VSphereError, :'too_many_private_networks' if private_networks.length > customization_spec.nicSettingMap.length - # assign the private network IP to the NIC - private_networks.each_index do |idx| - customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] + # assign the private network IP to the NIC + private_networks.each_index do |idx| + customization_spec.nicSettingMap[idx].adapter.ip.ipAddress = private_networks[idx][1][:ip] + end end customization_spec @@ -890,6 +919,64 @@ def configure_network_card(dc, adapter_configuration, adapter, label, summary) adapter end + def configure_disks(spec, dc, template, config) + # Enumerate lan cards + spec[:config][:deviceChange] ||= [] + + current_disks = Hash.new + + template.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk).each_with_index do |item, index| + current_disks[index] = item + end + + current_disks_length = current_disks.length + + # configuration may have gaps in it, so configuration may look like this: + # vsphere.disk 0, size: 5000 + # vsphere.disk 1, size: 10000 + # vsphere.disk 9, size: 90000 + config_disks_length = -1 + config.disks.each_with_index do |_item, index| + if index > config_disks_length + config_disks_length = index + end + end + config_disks_length += 1 + + number_of_existing_disks = current_disks_length + + # edit existing disks + if number_of_existing_disks > 0 + for index in (0).upto(number_of_existing_disks) + disk_configuration = config.disks[index] + puts "disk_configuration[#{index}]=#{disk_configuration.inspect}" + + # there may be no configuration for this disk so dont change it, if this is the case + unless disk_configuration.nil? + disk = current_disks[index] + disk = configure_disk(disk_configuration, disk) + + edit_disk = { + :operation => RbVmomi::VIM::VirtualDeviceConfigSpecOperation('edit'), + :device => disk + } + + spec[:config][:deviceChange].push edit_disk + spec[:config][:deviceChange].uniq! + end + end + end + + puts "spec[:config] = #{spec[:config].inspect}" + + spec + end + + def configure_disk(disk_configuration, disk) + disk.capacityInKB = disk_configuration.size + disk + end + def add_custom_memory(spec, memory_mb) spec[:config][:memoryMB] = Integer(memory_mb) end From 81d712f7d33743951c620aaa1594fe928c0af7c1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 24 Jul 2017 12:39:07 +0100 Subject: [PATCH 39/39] Write output to machines ui instead of env ui, so that when we are spinning up machines in parallel we know which machine the ui output is for --- lib/vSphere/action/clone.rb | 20 +++++++++--------- lib/vSphere/action/destroy.rb | 6 +++--- lib/vSphere/action/message_already_created.rb | 2 +- lib/vSphere/action/message_not_created.rb | 2 +- lib/vSphere/action/message_not_running.rb | 2 +- lib/vSphere/action/power_off.rb | 4 ++-- lib/vSphere/action/power_on.rb | 2 +- lib/vSphere/action/snapshot_delete.rb | 10 ++++----- lib/vSphere/action/snapshot_restore.rb | 10 ++++----- lib/vSphere/action/snapshot_save.rb | 10 ++++----- lib/vSphere/config.rb | 2 ++ lib/vSphere/driver.rb | 21 +++++++++++++------ 12 files changed, 51 insertions(+), 40 deletions(-) diff --git a/lib/vSphere/action/clone.rb b/lib/vSphere/action/clone.rb index 4feddf17..3a23cad1 100644 --- a/lib/vSphere/action/clone.rb +++ b/lib/vSphere/action/clone.rb @@ -14,24 +14,24 @@ def call(env) config = machine.provider_config driver = machine.provider.driver - env[:ui].info "Setting custom address: #{config.addressType}" unless config.addressType.nil? - env[:ui].info "Setting custom mac: #{config.mac}" unless config.mac.nil? - env[:ui].info "Setting custom vlan: #{config.vlan}" unless config.vlan.nil? - env[:ui].info "Setting custom memory: #{config.memory_mb}" unless config.memory_mb.nil? - env[:ui].info "Setting custom cpu count: #{config.cpu_count}" unless config.cpu_count.nil? - env[:ui].info "Setting custom cpu reservation: #{config.cpu_reservation}" unless config.cpu_reservation.nil? + machine.ui.info "Setting custom address: #{config.addressType}" unless config.addressType.nil? + machine.ui.info "Setting custom mac: #{config.mac}" unless config.mac.nil? + machine.ui.info "Setting custom vlan: #{config.vlan}" unless config.vlan.nil? + machine.ui.info "Setting custom memory: #{config.memory_mb}" unless config.memory_mb.nil? + machine.ui.info "Setting custom cpu count: #{config.cpu_count}" unless config.cpu_count.nil? + machine.ui.info "Setting custom cpu reservation: #{config.cpu_reservation}" unless config.cpu_reservation.nil? env[:ui].info "Setting custom memmory reservation: #{config.mem_reservation}" unless config.mem_reservation.nil? config.custom_attributes.each do |k, v| - env[:ui].info "Setting custom attribute: #{k}=#{v}" + machine.ui.info "Setting custom attribute: #{k}=#{v}" end driver.clone(env[:root_path]) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + machine.ui.clear_line + machine.ui.report_progress(progress, 100, false) end - env[:ui].info I18n.t('vsphere.vm_clone_success') + machine.ui.info I18n.t('vsphere.vm_clone_success') @app.call env end end diff --git a/lib/vSphere/action/destroy.rb b/lib/vSphere/action/destroy.rb index 56a9222b..85cde573 100644 --- a/lib/vSphere/action/destroy.rb +++ b/lib/vSphere/action/destroy.rb @@ -19,11 +19,11 @@ def call(env) private def destroy_vm(env) - env[:ui].info I18n.t('vsphere.destroy_vm') + env[:machine].ui.info I18n.t('vsphere.destroy_vm') env[:machine].provider.driver.destroy do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].ui.clear_line + env[:machine].ui.report_progress(progress, 100, false) end rescue Errors::VSphereError raise diff --git a/lib/vSphere/action/message_already_created.rb b/lib/vSphere/action/message_already_created.rb index 93e28062..ccfde002 100644 --- a/lib/vSphere/action/message_already_created.rb +++ b/lib/vSphere/action/message_already_created.rb @@ -9,7 +9,7 @@ def initialize(app, _env) end def call(env) - env[:ui].info I18n.t('vsphere.vm_already_created') + env[:machine].ui.info I18n.t('vsphere.vm_already_created') @app.call(env) end end diff --git a/lib/vSphere/action/message_not_created.rb b/lib/vSphere/action/message_not_created.rb index a20a6692..caacb909 100644 --- a/lib/vSphere/action/message_not_created.rb +++ b/lib/vSphere/action/message_not_created.rb @@ -9,7 +9,7 @@ def initialize(app, _env) end def call(env) - env[:ui].info I18n.t('vsphere.vm_not_created') + env[:machine].ui.info I18n.t('vsphere.vm_not_created') @app.call(env) end end diff --git a/lib/vSphere/action/message_not_running.rb b/lib/vSphere/action/message_not_running.rb index c8781b26..eceadbd8 100644 --- a/lib/vSphere/action/message_not_running.rb +++ b/lib/vSphere/action/message_not_running.rb @@ -9,7 +9,7 @@ def initialize(app, _env) end def call(env) - env[:ui].info I18n.t('vsphere.vm_not_running') + env[:machine].ui.info I18n.t('vsphere.vm_not_running') @app.call(env) end end diff --git a/lib/vSphere/action/power_off.rb b/lib/vSphere/action/power_off.rb index d8f6c066..7d767caa 100644 --- a/lib/vSphere/action/power_off.rb +++ b/lib/vSphere/action/power_off.rb @@ -16,13 +16,13 @@ def call(env) # that the Power Off task for a VM will fail if the state is not poweredOn # see: https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.VirtualMachine.html#powerOff if driver.suspended? - env[:ui].info I18n.t('vsphere.power_on_vm') + env[:machine].ui.info I18n.t('vsphere.power_on_vm') driver.power_on_vm end # Powering off is a no-op if we can't find the VM or if it is already off unless driver.powered_off?.nil? || driver.powered_off? - env[:ui].info I18n.t('vsphere.power_off_vm') + env[:machine].ui.info I18n.t('vsphere.power_off_vm') driver.power_off_vm end diff --git a/lib/vSphere/action/power_on.rb b/lib/vSphere/action/power_on.rb index f7648024..5e565c6b 100644 --- a/lib/vSphere/action/power_on.rb +++ b/lib/vSphere/action/power_on.rb @@ -10,7 +10,7 @@ def initialize(app, _env) end def call(env) - env[:ui].info I18n.t('vsphere.power_on_vm') + env[:machine].ui.info I18n.t('vsphere.power_on_vm') env[:machine].provider.driver.power_on_vm @app.call env diff --git a/lib/vSphere/action/snapshot_delete.rb b/lib/vSphere/action/snapshot_delete.rb index 2a9b58aa..bf3e63ad 100644 --- a/lib/vSphere/action/snapshot_delete.rb +++ b/lib/vSphere/action/snapshot_delete.rb @@ -9,15 +9,15 @@ def initialize(app, _env) def call(env) snapshot_name = env[:snapshot_name] - env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.deleting", name: snapshot_name)) + env[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.deleting", name: snapshot_name)) env[:machine].provider.driver.delete_snapshot(snapshot_name) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].ui.clear_line + env[:machine].uireport_progress(progress, 100, false) end - env[:ui].clear_line - env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.deleted", name: snapshot_name)) + env[:machine].ui.clear_line + env[:machine].ui.success(I18n.t("vagrant.actions.vm.snapshot.deleted", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/action/snapshot_restore.rb b/lib/vSphere/action/snapshot_restore.rb index a5016993..12f47083 100644 --- a/lib/vSphere/action/snapshot_restore.rb +++ b/lib/vSphere/action/snapshot_restore.rb @@ -9,15 +9,15 @@ def initialize(app, _env) def call(env) snapshot_name = env[:snapshot_name] - env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.restoring", name: snapshot_name)) + env[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.restoring", name: snapshot_name)) env[:machine].provider.driver.restore_snapshot(snapshot_name) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].ui.clear_line + env[:machine].ui.report_progress(progress, 100, false) end - env[:ui].clear_line - # env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.restored", name: snapshot_name)) + env[:machine].ui.clear_line + # env[:machine].ui.success(I18n.t("vagrant.actions.vm.snapshot.restored", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/action/snapshot_save.rb b/lib/vSphere/action/snapshot_save.rb index 558134e0..57fe3299 100644 --- a/lib/vSphere/action/snapshot_save.rb +++ b/lib/vSphere/action/snapshot_save.rb @@ -9,15 +9,15 @@ def initialize(app, _env) def call(env) snapshot_name = env[:snapshot_name] - env[:ui].info(I18n.t("vagrant.actions.vm.snapshot.saving", name: snapshot_name)) + env[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.saving", name: snapshot_name)) env[:machine].provider.driver.create_snapshot(snapshot_name) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].ui.clear_line + env[:machine].ui.report_progress(progress, 100, false) end - env[:ui].clear_line - env[:ui].success(I18n.t("vagrant.actions.vm.snapshot.saved", name: snapshot_name)) + env[:machine].ui.clear_line + env[:machine].ui.success(I18n.t("vagrant.actions.vm.snapshot.saved", name: snapshot_name)) @app.call env end diff --git a/lib/vSphere/config.rb b/lib/vSphere/config.rb index 6e52b867..555f5e0a 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -42,6 +42,8 @@ class DiskConfiguration def initialize(disk_config) @size = nil + + @size = disk_config[:size] if disk_config.key?(:size) end end diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb index daa25ce1..0af163c4 100644 --- a/lib/vSphere/driver.rb +++ b/lib/vSphere/driver.rb @@ -838,7 +838,7 @@ def configure_network_cards(spec, dc, template, config) # edit existing network interfaces if number_of_existing_adapters > 0 - for index in (0).upto(number_of_existing_adapters) + for index in (0).upto(number_of_existing_adapters-1) adapter_configuration = config.network_adapters[index] puts "adapter_configuration[#{index}]=#{adapter_configuration.inspect}" @@ -945,14 +945,19 @@ def configure_disks(spec, dc, template, config) number_of_existing_disks = current_disks_length + puts "config.disks=#{config.disks.inspect}" + puts "number_of_existing_disks=#{number_of_existing_disks}" # edit existing disks if number_of_existing_disks > 0 - for index in (0).upto(number_of_existing_disks) + for index in (0).upto(number_of_existing_disks-1) disk_configuration = config.disks[index] - puts "disk_configuration[#{index}]=#{disk_configuration.inspect}" + puts "disk_configuration=#{disk_configuration.inspect}" + puts "index=#{index}" + # there may be no configuration for this disk so dont change it, if this is the case - unless disk_configuration.nil? + unless disk_configuration.nil? || disk_configuration.size.nil? + puts "start device change added" disk = current_disks[index] disk = configure_disk(disk_configuration, disk) @@ -963,17 +968,21 @@ def configure_disks(spec, dc, template, config) spec[:config][:deviceChange].push edit_disk spec[:config][:deviceChange].uniq! + + puts "finshed device change added" end end end - puts "spec[:config] = #{spec[:config].inspect}" + puts "we exited!" spec end def configure_disk(disk_configuration, disk) - disk.capacityInKB = disk_configuration.size + unless disk_configuration.size.nil? + disk.capacityInKB = disk_configuration.size + end disk end