diff --git a/lib/fog/vsphere/compute.rb b/lib/fog/vsphere/compute.rb index 214bfc2..d0fd3bb 100644 --- a/lib/fog/vsphere/compute.rb +++ b/lib/fog/vsphere/compute.rb @@ -43,6 +43,7 @@ class Compute < Fog::Service model :customfield collection :customfields model :scsicontroller + model :nvmecontroller model :process model :cdrom collection :cdroms @@ -111,6 +112,7 @@ class Compute < Fog::Service request :list_customfields request :get_vm_first_scsi_controller request :list_vm_scsi_controllers + request :list_vm_nvme_controllers request :set_vm_customvalue request :vm_take_snapshot request :list_vm_snapshots @@ -136,6 +138,7 @@ class Compute < Fog::Service request :host_start_maintenance request :host_finish_maintenance request :get_vm_first_sata_controller + request :get_vm_first_nvme_controller module Shared attr_reader :vsphere_is_vcenter @@ -346,10 +349,12 @@ def is_uuid?(id) end end + # rubocop:disable Metrics/ClassLength class Mock include Shared # rubocop:disable Metrics/MethodLength def self.data + # rubocop:disable Metrics/BlockLength @data ||= Hash.new do |hash, key| hash[key] = { servers: { @@ -380,6 +385,11 @@ def self.data 'type' => 'VirtualLsiLogicController', 'unit_number' => 7, 'key' => 1000 }], + 'nvme_controllers' => + [{ + 'type' => 'VirtualNVMEController', + 'key' => 2000 + }], 'interfaces' => [{ 'mac' => '00:50:56:a9:00:28', 'network' => 'dvportgroup-123456', @@ -616,6 +626,7 @@ def self.data } } end + # rubocop:enable Metrics/BlockLength end # rubocop:enable Metrics/MethodLength @@ -637,6 +648,7 @@ def reset_data self.class.data.delete(@vsphere_username) end end + # rubocop:enable Metrics/ClassLength class Real include Shared diff --git a/lib/fog/vsphere/models/compute/nvmecontroller.rb b/lib/fog/vsphere/models/compute/nvmecontroller.rb new file mode 100644 index 0000000..acc0d51 --- /dev/null +++ b/lib/fog/vsphere/models/compute/nvmecontroller.rb @@ -0,0 +1,24 @@ +module Fog + module Vsphere + class Compute + class NVMEController < Fog::Model + attribute :type + attribute :unit_number + attribute :key, type: :integer + attribute :server_id + DEFAULT_KEY = 2000 + DEFAULT_TYPE = "VirtualNVMEController".freeze + + def initialize(attributes = {}) + super + self.key ||= DEFAULT_KEY + self.type ||= DEFAULT_TYPE + end + + def to_s + "#{type} ##{key}:, unit_number: #{unit_number}" + end + end + end + end +end diff --git a/lib/fog/vsphere/models/compute/scsicontroller.rb b/lib/fog/vsphere/models/compute/scsicontroller.rb index 393fcbc..7f39073 100644 --- a/lib/fog/vsphere/models/compute/scsicontroller.rb +++ b/lib/fog/vsphere/models/compute/scsicontroller.rb @@ -8,9 +8,13 @@ class SCSIController < Fog::Model attribute :key, type: :integer attribute :server_id + DEFAULT_KEY = 1000 + DEFAULT_TYPE = "VirtualLsiLogicController".freeze + def initialize(attributes = {}) super - self.key ||= 1000 + self.key ||= DEFAULT_KEY + self.type ||= DEFAULT_TYPE end def to_s diff --git a/lib/fog/vsphere/models/compute/server.rb b/lib/fog/vsphere/models/compute/server.rb index c5ff030..acbefcb 100644 --- a/lib/fog/vsphere/models/compute/server.rb +++ b/lib/fog/vsphere/models/compute/server.rb @@ -3,7 +3,7 @@ module Fog module Vsphere class Compute - class Server < Fog::Compute::Server + class Server < Fog::Compute::Server # rubocop:disable Metrics/ClassLength extend Fog::Deprecation deprecate(:ipaddress, :public_ip_address) deprecate(:scsi_controller, :scsi_controllers) @@ -49,6 +49,7 @@ class Server < Fog::Compute::Server attribute :guest_id attribute :hardware_version attribute :scsi_controllers, type: :array + attribute :nvme_controllers, type: :array attribute :cpuHotAddEnabled attribute :memoryHotAddEnabled attribute :firmware @@ -60,9 +61,10 @@ def initialize(attributes = {}) super defaults.merge(attributes) self.instance_uuid ||= id # TODO: remvoe instance_uuid as it can be replaced with simple id initialize_interfaces - initialize_volumes initialize_customvalues initialize_scsi_controllers + initialize_nvme_controllers + initialize_volumes end # Lazy Loaded Attributes @@ -289,6 +291,10 @@ def scsi_controllers attributes[:scsi_controllers] ||= service.list_vm_scsi_controllers(id) end + def nvme_controllers + attributes[:nvme_controllers] ||= service.list_vm_nvme_controllers(id) + end + def scsi_controller scsi_controllers.first end @@ -348,16 +354,21 @@ def initialize_interfaces end end + def unassigned_volumes? + attributes[:volumes]&.any? { |vol| !vol.key?(:controller_key) } || false + end + + def update_controller_key(vol) + vol.controller_key ||= attributes[:scsi_controllers].first&.key || 1000 + end + def initialize_volumes - if attributes[:volumes] && attributes[:volumes].is_a?(Array) - attributes[:volumes].map! do |vol| - if vol.is_a?(Hash) - service.volumes.new({ server: self }.merge(vol)) - else - vol.server = self - vol - end - end + return unless attributes[:volumes].is_a?(Array) + attributes[:volumes].map! do |vol| + vol = service.volumes.new({ server: self }.merge(vol)) if vol.is_a?(Hash) + vol.server = self + update_controller_key(vol) + vol end end @@ -368,19 +379,23 @@ def initialize_customvalues end def initialize_scsi_controllers - if attributes[:scsi_controllers] && attributes[:scsi_controllers].is_a?(Array) + if attributes[:scsi_controllers].is_a?(Array) && !attributes[:scsi_controllers].empty? attributes[:scsi_controllers].map! do |controller| controller.is_a?(Hash) ? Fog::Vsphere::Compute::SCSIController.new(controller) : controller end - elsif attributes[:scsi_controller] && attributes[:scsi_controller].is_a?(Hash) + elsif attributes[:scsi_controller].is_a?(Hash) && !attributes[:scsi_controller].empty? attributes[:scsi_controllers] = [ Fog::Vsphere::Compute::SCSIController.new(attributes[:scsi_controller]) ] - elsif attributes[:volumes] && attributes[:volumes].is_a?(Array) && !attributes[:volumes].empty? - # Create a default scsi controller if there are any disks but no controller defined - attributes[:scsi_controllers] = [ - Fog::Vsphere::Compute::SCSIController.new - ] + end + attributes[:scsi_controllers] = [Fog::Vsphere::Compute::SCSIController.new] if !attributes[:scsi_controllers]&.any? && unassigned_volumes? + end + + def initialize_nvme_controllers + if attributes[:nvme_controllers].is_a?(Array) + attributes[:nvme_controllers].map! do |controller| + controller.is_a?(Hash) ? Fog::Vsphere::Compute::NVMEController.new(controller) : controller + end end end end diff --git a/lib/fog/vsphere/models/compute/volume.rb b/lib/fog/vsphere/models/compute/volume.rb index 98078d7..ea75252 100644 --- a/lib/fog/vsphere/models/compute/volume.rb +++ b/lib/fog/vsphere/models/compute/volume.rb @@ -126,8 +126,7 @@ def defaults { thin: true, name: 'Hard disk', - mode: 'persistent', - controller_key: 1000 + mode: 'persistent' } end diff --git a/lib/fog/vsphere/requests/compute/create_vm.rb b/lib/fog/vsphere/requests/compute/create_vm.rb index f873d05..4e9b585 100644 --- a/lib/fog/vsphere/requests/compute/create_vm.rb +++ b/lib/fog/vsphere/requests/compute/create_vm.rb @@ -138,10 +138,14 @@ def device_change(attributes) devices << nics.map { |nic| create_interface(nic, nics.index(nic), :add, attributes) } end - if (scsi_controllers = (attributes[:scsi_controllers] || attributes['scsi_controller'])) + if (scsi_controllers = attributes[:scsi_controllers] || attributes['scsi_controller']) devices << scsi_controllers.each_with_index.map { |controller, index| create_controller(controller, index) } end + if (nvme_controllers = attributes[:nvme_controllers]) + devices << nvme_controllers.each_with_index.map { |controller, index| create_controller(controller, index) } + end + if (disks = attributes[:volumes]) devices << disks.map { |disk| create_disk(disk, :add, storage_pod: get_storage_pod_from_volumes(attributes)) } end @@ -241,11 +245,15 @@ def create_interface(nic, index = 0, operation = :add, attributes = {}) end def create_controller(controller = nil, index = 0) + device_options = {} options = if controller controller_default_options.merge(controller.attributes) else controller_default_options - end + end + unless [RbVmomi::VIM::VirtualAHCIController, RbVmomi::VIM::VirtualNVMEController, "VirtualNVMEController"].include?(options[:type]) + device_options[:sharedBus] = controller_get_shared_from_options(options) + end controller_class = if options[:type].is_a? String Fog::Vsphere.class_from_string options[:type], 'RbVmomi::VIM' else @@ -254,8 +262,7 @@ def create_controller(controller = nil, index = 0) { operation: options[:operation], device: controller_class.new(key: options[:key] || (1000 + index), - busNumber: options[:bus_id] || index, - **(options[:type] == RbVmomi::VIM::VirtualAHCIController ? {} : {sharedBus: controller_get_shared_from_options(options)})) + busNumber: options[:bus_id] || index, **device_options) } end diff --git a/lib/fog/vsphere/requests/compute/get_vm_first_nvme_controller.rb b/lib/fog/vsphere/requests/compute/get_vm_first_nvme_controller.rb new file mode 100644 index 0000000..00b642d --- /dev/null +++ b/lib/fog/vsphere/requests/compute/get_vm_first_nvme_controller.rb @@ -0,0 +1,21 @@ +module Fog + module Vsphere + class Compute + class Real + def get_vm_first_nvme_controller(vm_id) + ctrl = get_vm_ref(vm_id).config.hardware.device.find { |device| device.is_a?(RbVmomi::VIM::VirtualNVMEController) } + raise Fog::Vsphere::Compute::NotFound, "No NVME controller found for #{vm_id}" unless ctrl + { + type: ctrl&.class.to_s, + device_info: ctrl&.deviceInfo, + bus_number: ctrl&.busNumber, + key: ctrl&.key + } + end + end + class Mock + def get_vm_first_nvme_controller(vm_id); end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/list_vm_nvme_controllers.rb b/lib/fog/vsphere/requests/compute/list_vm_nvme_controllers.rb new file mode 100644 index 0000000..0194cb2 --- /dev/null +++ b/lib/fog/vsphere/requests/compute/list_vm_nvme_controllers.rb @@ -0,0 +1,29 @@ +module Fog + module Vsphere + class Compute + class Real + def list_vm_nvme_controllers(vm_id) + list_vm_nvme_controllers_raw(vm_id).map do |raw_controller| + Fog::Vsphere::Compute::NVMEController.new(raw_controller) + end + end + + def list_vm_nvme_controllers_raw(vm_id) + get_vm_ref(vm_id).config.hardware.device.grep(RbVmomi::VIM::VirtualNVMEController).map do |ctrl| + { + type: ctrl.class.to_s, + key: ctrl.key + } + end + end + end + class Mock + def list_vm_nvme_controllers(vm_id) + raise Fog::Vsphere::Compute::NotFound, 'VM not Found' unless data[:servers].key?(vm_id) + return [] unless data[:servers][vm_id].key?('nvme_controllers') + data[:servers][vm_id]['nvme_controllers'].map { |h| h.merge(server_id: vm_id) } + end + end + end + end +end diff --git a/lib/fog/vsphere/requests/compute/vm_clone.rb b/lib/fog/vsphere/requests/compute/vm_clone.rb index f5ea369..3f6f8f2 100644 --- a/lib/fog/vsphere/requests/compute/vm_clone.rb +++ b/lib/fog/vsphere/requests/compute/vm_clone.rb @@ -886,8 +886,8 @@ def relocation_volume_backing(volume) ) end end - # rubocop:enable Metrics/ClassLength + class Mock include Shared def vm_clone(options = {}) diff --git a/tests/fixtures/vcr_cassettes/6_7/get_vm_first_nvme_controller.yml b/tests/fixtures/vcr_cassettes/6_7/get_vm_first_nvme_controller.yml new file mode 100644 index 0000000..bbbe79f --- /dev/null +++ b/tests/fixtures/vcr_cassettes/6_7/get_vm_first_nvme_controller.yml @@ -0,0 +1,105 @@ +--- +http_interactions: +- request: + method: post + uri: https://<%= vsphere_server %>/sdk + body: + encoding: UTF-8 + string: <_this type="SearchIndex">SearchIndex500daa1c-abaf-7fe3-1a4a-5ce47e6f2b0a11 + headers: + Content-Type: + - text/xml; charset=utf-8 + Soapaction: + - urn:vim25/6.7.3 + Cookie: + - vmware_soap_session="ca554773d7f03e2487b07784540fa10ecb8c09bc"; Path=/; HttpOnly; + Secure; + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 31 May 2024 14:02:16 GMT + Cache-Control: + - no-cache + Connection: + - Keep-Alive + Content-Type: + - text/xml; charset=utf-8 + Content-Length: + - '440' + body: + encoding: UTF-8 + string: |- + + + + vm-121946 + + + recorded_at: Fri, 31 May 2024 14:02:16 GMT +- request: + method: post + uri: https://<%= vsphere_server %>/sdk + body: + encoding: UTF-8 + string: <_this type="PropertyCollector">propertyCollectorVirtualMachineconfigvm-121946 + headers: + Content-Type: + - text/xml; charset=utf-8 + Soapaction: + - urn:vim25/6.7.3 + Cookie: + - vmware_soap_session="ca554773d7f03e2487b07784540fa10ecb8c09bc"; Path=/; HttpOnly; + Secure; + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Date: + - Fri, 31 May 2024 14:02:16 GMT + Cache-Control: + - no-cache + Connection: + - Keep-Alive + Content-Type: + - text/xml; charset=utf-8 + Content-Length: + - '12072' + body: + encoding: UTF-8 + string: |- + + + + vm-121946config2024-05-30T12:35:08.949831Z1970-01-01T00:00:00Zrandom14.example.comOther (32-bit)vmx-14420da30b-a9da-159d-db36-9a9851556afc2024-05-30T12:35:08.652875Z500daa1c-abaf-7fe3-1a4a-5ce47e6f2b0atrueotherGuest[NFS-Node6] random14.example.com/random14.example.com.vmx[NFS-Node6] random14.example.com/[NFS-Node6] random14.example.com/[NFS-Node6] random14.example.com/0truetruetruetruemanualfalse0falsetruefalsefalsereleaseanyfalsefalsefalseautomatichvAutopowerOfffalseunsetfalsefalsefalsesofthardsoftsofthardsoftcheckpoint112048falsefalse200IDE 00201IDE 11300PS2 controller 00600700100PCI controller 0050012000310004000400SIO controller 00600Keyboard3000700Pointing device; Devicefalseautodetect3001500Video card100040961falsefalseautomatic26214412000Device on the virtual machine PCI bus that provides support for the virtual machine communication interface10017-1falsetrue31000NVME controller 010030032000320008,388,608 KB[NFS-Node6] random14.example.com/random14.example.com.vmdkdatastore-119816persistentfalsefalsetrue6000C29c-4fbf-8050-bc2f-f83bc18cdfb230c5149b7c80e41279774557fffffffefalsesharingNone310000838860885899345921000normal-11000normal01544-32000false4000DVSwitch: 50 0d a2 9d bd 74 1a 31-80 0b 9e 83 78 58 0d fb50 0d a2 9d bd 74 1a 31-80 0b 9e 83 78 58 0d fbdvportgroup-2719122418367659unsettruetruefalseuntried1007assigned00:50:56:8d:9b:63true050normal-1false0false-11000normal0false-120480normalnormalfalsefalsefalsenvramrandom14.example.com.nvrampciBridge0.presentTRUEsvga.presentTRUEpciBridge4.presentTRUEpciBridge4.virtualDevpcieRootPortpciBridge4.functions8pciBridge5.presentTRUEpciBridge5.virtualDevpcieRootPortpciBridge5.functions8pciBridge6.presentTRUEpciBridge6.virtualDevpcieRootPortpciBridge6.functions8pciBridge7.presentTRUEpciBridge7.virtualDevpcieRootPortpciBridge7.functions8hpet0.presentTRUEvmware.tools.internalversion0vmware.tools.requiredversion12294migrate.hostLogStatenonemigrate.migrationId0migrate.hostLograndom14.example.com-68620c73.hlogNFS-Node6/vmfs/volumes/152737cd-64160aefinherit0falsefalsefalse10000400032000ipv4falsefalsebios40falsefalse188940288190685184falsefalsenevernone0VYElW5/NuodLfmT3Kxl65BvABD0=falsefalseopportunistic + + + recorded_at: Fri, 31 May 2024 14:02:16 GMT +recorded_with: VCR 6.2.0 diff --git a/tests/requests/compute/get_vm_first_nvme_controller_tests.rb b/tests/requests/compute/get_vm_first_nvme_controller_tests.rb new file mode 100644 index 0000000..69ff64b --- /dev/null +++ b/tests/requests/compute/get_vm_first_nvme_controller_tests.rb @@ -0,0 +1,19 @@ +require_relative '../../test_helper' + +describe Fog::Vsphere::Compute::Real do + include Fog::Vsphere::TestHelper + + before { Fog.unmock! } + after { Fog.mock! } + + let(:compute) { prepare_compute } + + describe '#get_vm_first_nvme_controller' do + it 'gets virtual machine by uuid' do + with_webmock_cassette('get_vm_first_nvme_controller') do + controller = compute.get_vm_first_nvme_controller('500daa1c-abaf-7fe3-1a4a-5ce47e6f2b0a') + assert_equal(controller[:type], 'VirtualNVMEController') + end + end + end +end