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 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 diff --git a/README.md b/README.md index 7e96acec..536b0181 100644 --- a/README.md +++ b/README.md @@ -100,60 +100,123 @@ 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 - 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`. +* `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. +* `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 +* `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 + +## Network card configuration +* `slot` - integer - zero based array of the card index +* `allow_guest_control` - _Optional_ boolean - Configure the address type of the +* `connected` - _Optional_ boolean - is the vm network card 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 +* `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) +* `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 + +## 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 + 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 +* `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 + 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' +* `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. +* `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. + +### pipe +* `endpoint` - _Optional_ string - Indicates the role the virtual machine assumes as an endpoint for the pipe. + Possible values are 'client' and 'server' +* `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 +* `file_name` - _Optional_ string - Filename for the host file used in this backing. + +### device +* `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/action.rb b/lib/vSphere/action.rb index ad7891a9..0e2aa543 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,8 +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 b.use SyncedFolders @@ -121,7 +117,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 +134,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,33 +150,12 @@ 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 if Gem::Version.new(Vagrant::VERSION) >= Gem::Version.new('1.8.0') 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 +163,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 +183,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 +196,6 @@ def self.action_snapshot_save b2.use MessageNotCreated end end - b.use CloseVSphere end end end # Vagrant > 1.8.0 guard @@ -252,11 +204,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') @@ -264,13 +212,11 @@ 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 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..3a23cad1 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,284 +12,27 @@ 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 - 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) - - 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 + driver = machine.provider.driver + + 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| + machine.ui.info "Setting custom attribute: #{k}=#{v}" 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] + driver.clone(env[:root_path]) do |progress| + machine.ui.clear_line + machine.ui.report_progress(progress, 100, false) 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 + machine.ui.info I18n.t('vsphere.vm_clone_success') + @app.call env 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..85cde573 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,18 +19,16 @@ 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? + env[:machine].ui.info I18n.t('vsphere.destroy_vm') - begin - env[:ui].info I18n.t('vsphere.destroy_vm') - vm.Destroy_Task.wait_for_completion - rescue Errors::VSphereError - raise - rescue StandardError => e - raise Errors::VSphereError.new, e.message + env[:machine].provider.driver.destroy do |progress| + env[:machine].ui.clear_line + env[:machine].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/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/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 9720f019..7d767caa 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) - env[:ui].info I18n.t('vsphere.power_on_vm') - power_on_vm(vm) + if driver.suspended? + 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 vm.nil? || powered_off?(vm) - env[:ui].info I18n.t('vsphere.power_off_vm') - power_off_vm(vm) + unless driver.powered_off?.nil? || driver.powered_off? + env[:machine].ui.info I18n.t('vsphere.power_off_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..5e565c6b 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].ui.info I18n.t('vsphere.power_on_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..bf3e63ad 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: env[:snapshot_name])) + env[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.deleting", name: snapshot_name)) - delete_snapshot(vm, env[:snapshot_name]) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].provider.driver.delete_snapshot(snapshot_name) do |progress| + env[:machine].ui.clear_line + env[:machine].uireport_progress(progress, 100, false) end - env[:ui].clear_line - - env[:ui].info(I18n.t( - "vagrant.actions.vm.snapshot.deleted", - name: env[: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_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..12f47083 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[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.restoring", name: snapshot_name)) - restore_snapshot(vm, env[:snapshot_name]) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].provider.driver.restore_snapshot(snapshot_name) do |progress| + env[:machine].ui.clear_line + env[:machine].ui.report_progress(progress, 100, false) end - env[:ui].clear_line + 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 ec054d69..57fe3299 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])) + env[:machine].ui.info(I18n.t("vagrant.actions.vm.snapshot.saving", name: snapshot_name)) - create_snapshot(vm, env[:snapshot_name]) do |progress| - env[:ui].clear_line - env[:ui].report_progress(progress, 100, false) + env[:machine].provider.driver.create_snapshot(snapshot_name) do |progress| + env[:machine].ui.clear_line + env[:machine].ui.report_progress(progress, 100, false) end - env[:ui].clear_line + env[:machine].ui.clear_line + env[:machine].ui.success(I18n.t("vagrant.actions.vm.snapshot.saved", name: snapshot_name)) - env[:ui].success(I18n.t( - "vagrant.actions.vm.snapshot.saved", - name: env[: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 deleted file mode 100644 index 2d9b2b63..00000000 --- a/lib/vSphere/action/wait_for_ip_address.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'ipaddr' -require 'timeout' - -module VagrantPlugins - module VSphere - 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 - - env[:ui].output('Waiting for the machine to report its IP address...') - env[:ui].detail("Timeout: #{timeout} seconds") - - guest_ip = nil - 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 guest_ip - begin - IPAddr.new(guest_ip) - break - rescue IPAddr::InvalidAddressError - # Ignore, continue looking. - @logger.warn("Invalid IP address returned: #{guest_ip}") - end - 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 - raise Errors::VSphereError, :wait_for_ip_address_timeout - end - end - 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/config.rb b/lib/vSphere/config.rb index 19d3efb5..555f5e0a 100644 --- a/lib/vSphere/config.rb +++ b/lib/vSphere/config.rb @@ -3,7 +3,114 @@ module VagrantPlugins module VSphere class Config < Vagrant.plugin('2', :config) - attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] + class NetworkConfiguration + attr_accessor :allow_guest_control + attr_accessor :connected + attr_accessor :start_connected + + attr_accessor :vlan + attr_accessor :address_type + attr_accessor :mac_address + attr_accessor :ip_address + attr_accessor :wake_on_lan_enabled + + def initialize(network_config) + @allow_guest_control = false + @connected = true + @start_connected = true + + @vlan = nil + @address_type = 'generated' + @mac_address = nil + @ip_address = nil + @wake_on_lan_enabled = false + + @allow_guest_control = network_config[:allow_guest_control] if network_config.key?(:allow_guest_control) + @connected = network_config[:connected] if network_config.key?(:connected) + @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].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) + end + end + + class DiskConfiguration + attr_accessor :size + + def initialize(disk_config) + @size = nil + + @size = disk_config[:size] if disk_config.key?(:size) + end + 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) + 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 attr_accessor :insecure attr_accessor :user @@ -20,36 +127,54 @@ 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_accessor :wait_for_customization + attr_accessor :wait_for_customization_timeout + 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 :disks + attr_reader :serial_ports attr_reader :custom_attributes def initialize - @ip_address_timeout = UNSET_VALUE - @wait_for_sysprep = UNSET_VALUE + @wait_for_customization_timeout = 600 + @destroy_unused_network_interfaces = UNSET_VALUE + @destroy_unused_serial_ports = UNSET_VALUE + @network_adapters = {} + @disks = {} @custom_attributes = {} + @serial_ports = {} @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 + 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 + + def finalize! + end + def validate(machine) errors = _detected_errors diff --git a/lib/vSphere/driver.rb b/lib/vSphere/driver.rb new file mode 100644 index 00000000..0af163c4 --- /dev/null +++ b/lib/vSphere/driver.rb @@ -0,0 +1,1018 @@ +require 'log4r' +require 'rbvmomi' +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 + 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 + + 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 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| + 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) + 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? + 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 + + puts 'vlan: ' + name + + 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 } + 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 + 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 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 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 + 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 + unless 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 do |item, index| + current_adapters[index] = item + end + + 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 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 + 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-1) + 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 + unless 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) + unless 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 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 + + 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-1) + disk_configuration = config.disks[index] + 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? || disk_configuration.size.nil? + puts "start device change added" + 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! + + puts "finshed device change added" + end + end + end + + puts "we exited!" + + spec + end + + def configure_disk(disk_configuration, disk) + unless disk_configuration.size.nil? + disk.capacityInKB = disk_configuration.size + end + disk + 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/provider.rb b/lib/vSphere/provider.rb index f30c48a4..ccc0fa63 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,37 @@ def action(name) nil end + # If the machine ID changed, then we need to rebuild our underlying + # driver. + def machine_id_changed + @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 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 diff --git a/locales/en.yml b/locales/en.yml index 79ece691..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: |- @@ -48,12 +48,10 @@ 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 - sysprep_timeout: |- + specified_mangement_interface_does_not_exist: |- + specified management interface does not exist + customization_timeout: |- Customization of VM not succeeded within timeout. - wait_for_ip_address_timeout: |- - Timeout while waiting for ip address config: host: |- 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'