Merge pull request #382 from puppetlabs/pooler-167

(POOLER-167) Allow for network configuration at vm clone time
This commit is contained in:
mattkirby 2020-06-23 14:30:25 -07:00 committed by GitHub
commit a2a3bb7dfd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 262 additions and 29 deletions

View file

@ -298,7 +298,6 @@ module Vmpooler
template_path = pool['template'] template_path = pool['template']
target_folder_path = pool['folder'] target_folder_path = pool['folder']
target_datastore = pool['datastore'] target_datastore = pool['datastore']
target_cluster_name = get_target_cluster_from_config(pool_name)
target_datacenter_name = get_target_datacenter_from_config(pool_name) target_datacenter_name = get_target_datacenter_from_config(pool_name)
# Get the template VM object # Get the template VM object
@ -320,31 +319,19 @@ module Vmpooler
] ]
) )
# Put the VM in the specified folder and resource pool # Check if alternate network configuration is specified and add configuration
relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec( if pool.key?('network')
datastore: find_datastore(target_datastore, connection, target_datacenter_name), template_vm_network_device = template_vm_object.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first
diskMoveType: get_disk_backing(pool) network_name = pool['network']
) network_device = set_network_device(target_datacenter_name, template_vm_network_device, network_name, connection)
config_spec.deviceChange = [{ operation: 'edit', device: network_device }]
manage_host_selection = @config[:config]['manage_host_selection'] if @config[:config].key?('manage_host_selection')
if manage_host_selection
run_select_hosts(pool_name, @provider_hosts)
target_host = select_next_host(pool_name, @provider_hosts)
host_object = find_host_by_dnsname(connection, target_host)
relocate_spec.host = host_object
else
# Choose a cluster/host to place the new VM on
target_cluster_object = find_cluster(target_cluster_name, connection, target_datacenter_name)
relocate_spec.pool = target_cluster_object.resourcePool
end end
# Put the VM in the specified folder and resource pool
relocate_spec = create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection)
# Create a clone spec # Create a clone spec
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec( clone_spec = create_clone_spec(relocate_spec, config_spec)
location: relocate_spec,
config: config_spec,
powerOn: true,
template: false
)
begin begin
vm_target_folder = find_vm_folder(pool_name, connection) vm_target_folder = find_vm_folder(pool_name, connection)
@ -370,6 +357,81 @@ module Vmpooler
vm_hash vm_hash
end end
def create_relocate_spec(target_datastore, target_datacenter_name, pool_name, connection)
pool = pool_config(pool_name)
target_cluster_name = get_target_cluster_from_config(pool_name)
relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec(
datastore: find_datastore(target_datastore, connection, target_datacenter_name),
diskMoveType: get_disk_backing(pool)
)
manage_host_selection = @config[:config]['manage_host_selection'] if @config[:config].key?('manage_host_selection')
if manage_host_selection
run_select_hosts(pool_name, @provider_hosts)
target_host = select_next_host(pool_name, @provider_hosts)
host_object = find_host_by_dnsname(connection, target_host)
relocate_spec.host = host_object
else
# Choose a cluster/host to place the new VM on
target_cluster_object = find_cluster(target_cluster_name, connection, target_datacenter_name)
relocate_spec.pool = target_cluster_object.resourcePool
end
relocate_spec
end
def create_clone_spec(relocate_spec, config_spec)
RbVmomi::VIM.VirtualMachineCloneSpec(
location: relocate_spec,
config: config_spec,
powerOn: true,
template: false
)
end
def set_network_device(datacenter_name, template_vm_network_device, network_name, connection)
# Retrieve network object
datacenter = connection.serviceInstance.find_datacenter(datacenter_name)
new_network = datacenter.network.find { |n| n.name == network_name }
raise("Cannot find network #{network_name} in datacenter #{datacenter_name}") unless new_network
# Determine network device type
# All possible device type options here: https://vdc-download.vmware.com/vmwb-repository/dcr-public/98d63b35-d822-47fe-a87a-ddefd469df06/2e3c7b58-f2bd-486e-8bb1-a75eb0640bee/doc/vim.vm.device.VirtualEthernetCard.html
network_device =
if template_vm_network_device.is_a? RbVmomi::VIM::VirtualVmxnet2
RbVmomi::VIM.VirtualVmxnet2
elsif template_vm_network_device.is_a? RbVmomi::VIM::VirtualVmxnet3
RbVmomi::VIM.VirtualVmxnet3
elsif template_vm_network_device.is_a? RbVmomi::VIM::VirtualE1000
RbVmomi::VIM.VirtualE1000
elsif template_vm_network_device.is_a? RbVmomi::VIM::VirtualE1000e
RbVmomi::VIM.VirtualE1000e
elsif template_vm_network_device.is_a? RbVmomi::VIM::VirtualSriovEthernetCard
RbVmomi::VIM.VirtualSriovEthernetCard
else
RbVmomi::VIM.VirtualPCNet32
end
# Set up new network device attributes
network_device.key = template_vm_network_device.key
network_device.deviceInfo = RbVmomi::VIM.Description(
label: template_vm_network_device.deviceInfo.label,
summary: network_name
)
network_device.backing = RbVmomi::VIM.VirtualEthernetCardNetworkBackingInfo(
deviceName: network_name,
network: new_network,
useAutoDetect: false
)
network_device.addressType = 'assigned'
network_device.connectable = RbVmomi::VIM.VirtualDeviceConnectInfo(
allowGuestControl: true,
startConnected: true,
connected: true
)
network_device
end
def create_disk(pool_name, vm_name, disk_size) def create_disk(pool_name, vm_name, disk_size)
pool = pool_config(pool_name) pool = pool_config(pool_name)
raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil? raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil?

View file

@ -63,6 +63,20 @@ MockDatacenter = Struct.new(
end end
end end
MockNetwork = Struct.new(
# https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Network.html
# From Network
:host, :name, :summary, :vm
)
MockVirtualVmxnet3 = Struct.new(
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.device.VirtualVmxnet.html
# From VirtualEthenetCard
:addressType,
# From VirtualDevice
:key, :deviceInfo, :backing, :connectable
)
MockDatastore = Struct.new( MockDatastore = Struct.new(
# https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Datastore.html # https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Datastore.html
# From Datastore # From Datastore
@ -208,6 +222,33 @@ MockDescription = Struct.new(
:label, :summary :label, :summary
) )
MockVirtualEthernetCardNetworkBackingInfo = Struct.new(
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.device.VirtualEthernetCard.NetworkBackingInfo.html
# From VirtualEthernetCardNetworkBackingInfo
:network,
# From VirtualDeviceBackingInfo
:deviceName, :useAutoDetect
)
MockVirtualDeviceConnectInfo = Struct.new(
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.device.VirtualDevice.ConnectInfo.html
# From VirtualDeviceConnectInfo
:allowGuestControl, :connected, :startConnected
)
MockVirtualMachineConfigSpec = Struct.new(
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.ConfigSpec.html
# From VirtualMachineConfigSpec
:deviceChange, :annotation, :extraConfig
)
MockVirtualMachineRelocateSpec = Struct.new(
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.RelocateSpec.html
# From VirtualMachineRelocateSpec
:datastore, :diskMoveType, :pool
)
MockDynamicProperty = Struct.new( MockDynamicProperty = Struct.new(
# https://pubs.vmware.com/vsphere-55/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvmodl.DynamicProperty.html # https://pubs.vmware.com/vsphere-55/index.jsp?topic=%2Fcom.vmware.wssdk.apiref.doc%2Fvmodl.DynamicProperty.html
# From DynamicProperty # From DynamicProperty
@ -433,6 +474,7 @@ def mock_RbVmomi_VIM_Datacenter(options = {})
# Currently don't support mocking datastore tree # Currently don't support mocking datastore tree
options[:datastores] = [] if options[:datastores].nil? options[:datastores] = [] if options[:datastores].nil?
options[:name] = 'Datacenter' + rand(65536).to_s if options[:name].nil? options[:name] = 'Datacenter' + rand(65536).to_s if options[:name].nil?
options[:networks] = [] if options[:networks].nil?
mock = MockDatacenter.new() mock = MockDatacenter.new()
@ -440,6 +482,7 @@ def mock_RbVmomi_VIM_Datacenter(options = {})
mock.hostFolder = mock_RbVmomi_VIM_Folder({ :name => 'hostFolderRoot'}) mock.hostFolder = mock_RbVmomi_VIM_Folder({ :name => 'hostFolderRoot'})
mock.vmFolder = mock_RbVmomi_VIM_Folder({ :name => 'vmFolderRoot'}) mock.vmFolder = mock_RbVmomi_VIM_Folder({ :name => 'vmFolderRoot'})
mock.datastore = [] mock.datastore = []
mock.network = []
# Create vmFolder hierarchy # Create vmFolder hierarchy
recurse_folder_tree(options[:vmfolder_tree],mock.vmFolder.childEntity) recurse_folder_tree(options[:vmfolder_tree],mock.vmFolder.childEntity)
@ -453,6 +496,12 @@ def mock_RbVmomi_VIM_Datacenter(options = {})
mock.datastore << mock_ds mock.datastore << mock_ds
end end
# Create mock Networks
options[:networks].each do |networkname|
mock_nw = mock_RbVmomi_VIM_Network({ :name => networkname })
mock.network << mock_nw
end
allow(mock).to receive(:is_a?) do |expected_type| allow(mock).to receive(:is_a?) do |expected_type|
expected_type == RbVmomi::VIM::Datacenter expected_type == RbVmomi::VIM::Datacenter
end end
@ -508,6 +557,72 @@ def mock_RbVmomi_VIM_Datastore(options = {})
mock mock
end end
# https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Network.html
def mock_RbVmomi_VIM_Network(options = {})
options[:name] = 'Network' + rand(65536).to_s if options[:name].nil?
mock = MockNetwork.new()
mock.name = options[:name]
allow(mock).to receive(:is_a?) do |expected_type|
expected_type == RbVmomi::VIM::Network
end
mock
end
# https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.device.VirtualVmxnet3.html
def mock_RbVmomi_VIM_VirtualVmxnet3(options = {})
options[:key] = rand(65536) if options[:key].nil?
options[:deviceInfo] = MockDescription.new()
options[:backing] = MockVirtualEthernetCardNetworkBackingInfo.new()
options[:addressType] = 'assigned'
options[:connectable] = MockVirtualDeviceConnectInfo.new()
mock = MockVirtualVmxnet3.new()
mock.key = options[:key]
mock.deviceInfo = options[:deviceInfo]
mock.backing = options[:backing]
mock.addressType = options[:addressType]
mock.connectable = options[:connectable]
allow(mock).to receive(:is_a?) do |expected_type|
expected_type == RbVmomi::VIM::VirtualVmxnet3
end
mock
end
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.RelocateSpec.html
def mock_RbVmomi_VIM_VirtualMachineRelocateSpec(options = {})
options[:datastore] = 'Datastore' + rand(65536).to_s if options[:datastore].nil?
options[:diskMoveType] = :moveChildMostDiskBacking
options[:pool] = 'Pool' + rand(65536).to_s if options[:pool].nil?
mock = MockVirtualMachineRelocateSpec.new
mock.datastore = mock_RbVmomi_VIM_Datastore({ :name => options[:datastore]})
mock.diskMoveType = options[:diskMoveType]
mock.pool = mock_RbVmomi_VIM_ResourcePool({:name => options[:pool]})
allow(mock).to receive(:is_a?).and_return(RbVmomi::VIM::VirtualMachineRelocateSpec)
mock
end
# https://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.ConfigSpec.html
def mock_RbVmomi_VIM_VirtualMachineConfigSpec(options = {})
options[:device] = mock_RbVmomi_VIM_VirtualVmxnet3()
mock = MockVirtualMachineConfigSpec.new
mock.deviceChange = []
mock.deviceChange << { operation: :edit, device: options[:device]}
mock
end
# https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Datastore.html # https://www.vmware.com/support/developer/vc-sdk/visdk41pubs/ApiReference/vim.Datastore.html
def mock_RbVmomi_VIM_Folder(options = {}) def mock_RbVmomi_VIM_Folder(options = {})
options[:name] = 'Folder' + rand(65536).to_s if options[:name].nil? options[:name] = 'Folder' + rand(65536).to_s if options[:name].nil?

View file

@ -648,7 +648,6 @@ EOT
context 'when create_vm_folder returns nil' do context 'when create_vm_folder returns nil' do
before(:each) do before(:each) do
template_vm = new_template_object
allow(subject).to receive(:find_template_vm).and_return(new_template_object) allow(subject).to receive(:find_template_vm).and_return(new_template_object)
expect(subject).to receive(:find_vm_folder).and_return(nil) expect(subject).to receive(:find_vm_folder).and_return(nil)
end end
@ -691,6 +690,43 @@ EOT
end end
end end
describe '#set_network_device' do
let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({
:datastores => ['datastore0'],
:networks => ['network0'],
})
}
let(:template_vm_network_device) { mock_RbVmomi_VIM_VirtualVmxnet3() }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
allow(connection.serviceInstance).to receive(:find_datacenter).and_return(datacenter_object)
end
context 'Given an invalid network name' do
network_name = "invalid_network"
it 'should raise an error' do
expect { subject.set_network_device("datacenter_name", template_vm_network_device, network_name, connection)}.to raise_error(/Cannot find network/)
end
end
context 'Given a valid network name' do
network_name = "network0"
it 'should return the network device' do
result = subject.set_network_device("datacenter_name", template_vm_network_device, network_name, connection)
expect(result).to be_instance_of(RbVmomi::VIM::VirtualVmxnet3)
expect(result.deviceInfo).to be_instance_of(RbVmomi::VIM::Description)
expect(result.deviceInfo.summary).to eq('network0')
expect(result.backing).to be_instance_of(RbVmomi::VIM::VirtualEthernetCardNetworkBackingInfo)
expect(result.backing.network.is_a?(RbVmomi::VIM::Network)).to be true
expect(result.backing.network.name).to eq('network0')
expect(result.connectable).to be_instance_of(RbVmomi::VIM::VirtualDeviceConnectInfo)
expect(result.addressType).to eq('assigned')
end
end
end
describe '#create_disk' do describe '#create_disk' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) } let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
let(:datastorename) { 'datastore0' } let(:datastorename) { 'datastore0' }
@ -1144,7 +1180,7 @@ EOT
let (:credentials) { config[:providers][:vsphere] } let (:credentials) { config[:providers][:vsphere] }
context 'succesful connection' do context 'successful connection' do
it 'should use the supplied credentials' do it 'should use the supplied credentials' do
expect(RbVmomi::VIM).to receive(:connect).with({ expect(RbVmomi::VIM).to receive(:connect).with({
:host => credentials['server'], :host => credentials['server'],
@ -1436,7 +1472,7 @@ EOT
allow(reconfig_vm_task).to receive(:wait_for_completion).and_return(true) allow(reconfig_vm_task).to receive(:wait_for_completion).and_return(true)
end end
context 'Succesfully addding disk' do context 'Successfully adding disk' do
it 'should return true' do it 'should return true' do
expect(subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name)).to be true expect(subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name)).to be true
end end
@ -1501,6 +1537,26 @@ EOT
end end
end end
describe '#create_clone_spec' do
let(:relocate_spec) { mock_RbVmomi_VIM_VirtualMachineRelocateSpec({
:datastore => 'datastore0',
:diskMoveType => :moveChildMostDiskBacking,
:pool => 'pool0'
})
}
let(:config_spec) { mock_RbVmomi_VIM_VirtualMachineConfigSpec()}
it 'should return the configured clone spec' do
result = subject.create_clone_spec(relocate_spec, config_spec)
expect(result.location.pool.name).to eq('pool0')
expect(result.location.datastore.name).to eq('datastore0')
expect(result.location.diskMoveType).to eq(:moveChildMostDiskBacking)
expect(result.config.deviceChange.first[:operation]).to eq(:edit)
expect(result.config.deviceChange.first[:device].is_a?(RbVmomi::VIM::VirtualVmxnet3)).to be true
end
end
describe '#find_datastore' do describe '#find_datastore' do
let(:datastorename) { 'datastore' } let(:datastorename) { 'datastore' }
let(:datastore_list) { [] } let(:datastore_list) { [] }
@ -2378,7 +2434,7 @@ EOT
end end
it 'should return the second host if called twice' do it 'should return the second host if called twice' do
result = subject.select_next_host(poolname, hosts_hash) subject.select_next_host(poolname, hosts_hash)
result2 = subject.select_next_host(poolname, hosts_hash) result2 = subject.select_next_host(poolname, hosts_hash)
expect(result2).to eq('host2') expect(result2).to eq('host2')
end end
@ -2391,7 +2447,7 @@ EOT
end end
it 'should return the second host if called twice' do it 'should return the second host if called twice' do
result = subject.select_next_host(poolname, hosts_hash, architecture) subject.select_next_host(poolname, hosts_hash, architecture)
result2 = subject.select_next_host(poolname, hosts_hash, architecture) result2 = subject.select_next_host(poolname, hosts_hash, architecture)
expect(result2).to eq('host4') expect(result2).to eq('host4')
end end