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-5ce47e6f2b0atruefalseotherGuest[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