vmpooler/spec/unit/providers/vsphere_spec.rb
Glenn Sarti 7077eadc65 (POOLER-83) Add ability to specify a datacenter for vsphere
Previously the vsphere provider assumed that there was one and only one
datacenter (DC) in the vsphere instance.  However this is simply not true for
many vSphere installations.  This commit:
- Adds the ability to define a vSphere datacenter at the Pool or Provider level
  whereby the Pool setting takes precedence
- If no datacenter is specified the default behaviour of picking the first DC
  in the vSphere instance
- Updated all tests for the new setting
- Update the vmpooler configuration file example with relevant setting name
  and expected behaviour
- Fixed a bug in the rvmomi_helper whereby if no DC was found it would return
  all DCs.  This is opposite behaviour of the real RBVMOMI library as it returns
  nil
2017-06-20 15:48:30 -07:00

3111 lines
106 KiB
Ruby

require 'spec_helper'
RSpec::Matchers.define :relocation_spec_with_host do |value|
match { |actual| actual[:spec].host == value }
end
RSpec::Matchers.define :create_virtual_disk_with_size do |value|
match { |actual| actual[:spec].capacityKb == value * 1024 * 1024 }
end
RSpec::Matchers.define :create_vm_spec do |new_name,target_folder_name,datastore|
match { |actual|
# Should have the correct new name
actual[:name] == new_name &&
# Should be in the new folder
actual[:folder].name == target_folder_name &&
# Should be poweredOn after clone
actual[:spec].powerOn == true &&
# Should be on the correct datastore
actual[:spec][:location].datastore.name == datastore &&
# Should contain annotation data
actual[:spec][:config].annotation != '' &&
# Should contain VIC information
actual[:spec][:config].extraConfig[0][:key] == 'guestinfo.hostname' &&
actual[:spec][:config].extraConfig[0][:value] == new_name
}
end
RSpec::Matchers.define :create_snapshot_spec do |new_snapshot_name|
match { |actual|
# Should have the correct new name
actual[:name] == new_snapshot_name &&
# Should snapshot the memory too
actual[:memory] == true &&
# Should quiesce the disk
actual[:quiesce] == true
}
end
describe 'Vmpooler::PoolManager::Provider::VSphere' do
let(:logger) { MockLogger.new }
let(:metrics) { Vmpooler::DummyStatsd.new }
let(:poolname) { 'pool1'}
let(:provider_options) { { 'param' => 'value' } }
let(:datacenter_name) { 'MockDC' }
let(:config) { YAML.load(<<-EOT
---
:config:
max_tries: 3
retry_factor: 10
:providers:
:vsphere:
server: "vcenter.domain.local"
username: "vcenter_user"
password: "vcenter_password"
insecure: true
# Drop the connection pool timeout way down for spec tests so they fail fast
connection_pool_timeout: 1
datacenter: MockDC
:pools:
- name: '#{poolname}'
alias: [ 'mockpool' ]
template: 'Templates/pool1'
folder: 'Pooler/pool1'
datastore: 'datastore0'
size: 5
timeout: 10
ready_ttl: 1440
clone_target: 'cluster1'
EOT
)
}
let(:connection_options) {{}}
let(:connection) { mock_RbVmomi_VIM_Connection(connection_options) }
let(:vmname) { 'vm1' }
subject { Vmpooler::PoolManager::Provider::VSphere.new(config, logger, metrics, 'vsphere', provider_options) }
before(:each) do
allow(subject).to receive(:vsphere_connection_ok?).and_return(true)
end
describe '#name' do
it 'should be vsphere' do
expect(subject.name).to eq('vsphere')
end
end
describe '#vms_in_pool' do
let(:folder_object) { mock_RbVmomi_VIM_Folder({ :name => 'pool1'}) }
let(:pool_config) { config[:pools][0] }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
end
context 'Given a pool folder that is missing' do
before(:each) do
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(nil)
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.vms_in_pool(poolname)
end
it 'should return an empty array' do
result = subject.vms_in_pool(poolname)
expect(result).to eq([])
end
end
context 'Given an empty pool folder' do
before(:each) do
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object)
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.vms_in_pool(poolname)
end
it 'should return an empty array' do
result = subject.vms_in_pool(poolname)
expect(result).to eq([])
end
end
context 'Given a pool folder with many VMs' do
let(:expected_vm_list) {[
{ 'name' => 'vm1'},
{ 'name' => 'vm2'},
{ 'name' => 'vm3'}
]}
before(:each) do
expected_vm_list.each do |vm_hash|
mock_vm = mock_RbVmomi_VIM_VirtualMachine({ :name => vm_hash['name'] })
# Add the mocked VM to the folder
folder_object.childEntity << mock_vm
end
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object)
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.vms_in_pool(poolname)
end
it 'should list all VMs in the VM folder for the pool' do
result = subject.vms_in_pool(poolname)
expect(result).to eq(expected_vm_list)
end
end
end
describe '#get_vm_host' do
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
expect(subject).to receive(:find_vm).with(vmname,connection).and_return(vm_object)
end
context 'when VM does not exist' do
let(:vm_object) { nil }
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.get_vm_host(poolname,vmname)
end
it 'should return nil' do
expect(subject.get_vm_host(poolname,vmname)).to be_nil
end
end
context 'when VM exists but missing runtime information' do
# For example if the VM is shutdown
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
}
before(:each) do
vm_object.summary.runtime = nil
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.get_vm_host(poolname,vmname)
end
it 'should return nil' do
expect(subject.get_vm_host(poolname,vmname)).to be_nil
end
end
context 'when VM exists and is running on a host' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
}
let(:hostname) { 'HOST001' }
before(:each) do
mock_host = mock_RbVmomi_VIM_HostSystem({ :name => hostname })
vm_object.summary.runtime.host = mock_host
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.get_vm_host(poolname,vmname)
end
it 'should return the hostname' do
expect(subject.get_vm_host(poolname,vmname)).to eq(hostname)
end
end
end
describe '#find_least_used_compatible_host' do
let(:vm_object) { nil }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
expect(subject).to receive(:find_vm).with(vmname,connection).and_return(vm_object)
end
context 'when VM does not exist' do
let(:vm_object) { nil }
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.find_least_used_compatible_host(poolname,vmname)
end
it 'should return nil' do
expect(subject.find_least_used_compatible_host(poolname,vmname)).to be_nil
end
end
context 'when VM exists but no compatible host' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
let(:host_list) { nil }
before(:each) do
expect(subject).to receive(:find_least_used_vpshere_compatible_host).with(vm_object).and_return(host_list)
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.find_least_used_compatible_host(poolname,vmname)
end
it 'should return nil' do
expect(subject.find_least_used_compatible_host(poolname,vmname)).to be_nil
end
end
context 'when VM exists and a compatible host' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
let(:hostname) { 'HOST001' }
# As per find_least_used_vpshere_compatible_host, the return value is an array
# [ <HostObject>, <Hostname> ]
let(:host_list) { [mock_RbVmomi_VIM_HostSystem({ :name => hostname }), hostname] }
before(:each) do
expect(subject).to receive(:find_least_used_vpshere_compatible_host).with(vm_object).and_return(host_list)
end
it 'should get a connection' do
expect(subject).to receive(:connect_to_vsphere).and_return(connection)
subject.find_least_used_compatible_host(poolname,vmname)
end
it 'should return the hostname' do
expect(subject.find_least_used_compatible_host(poolname,vmname)).to eq(hostname)
end
end
end
describe '#migrate_vm_to_host' do
let(:dest_host_name) { 'HOST002' }
let(:cluster_name) { 'CLUSTER001' }
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
}
before(:each) do
config[:pools][0]['clone_target'] = cluster_name
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
allow(subject).to receive(:find_vm).and_return(vm_object)
end
context 'Given an invalid pool name' do
it 'should raise an error' do
expect{ subject.migrate_vm_to_host('missing_pool', vmname, dest_host_name) }.to raise_error(/missing_pool does not exist/)
end
end
context 'Given a missing VM name' do
before(:each) do
expect(subject).to receive(:find_vm).and_return(nil)
end
it 'should raise an error' do
expect{ subject.migrate_vm_to_host(poolname, 'missing_vm', dest_host_name) }.to raise_error(/missing_vm does not exist/)
end
end
context 'Given a missing cluster name in the pool configuration' do
let(:cluster_name) { 'missing_cluster' }
before(:each) do
config[:pools][0]['clone_target'] = cluster_name
expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(nil)
end
it 'should raise an error' do
expect{ subject.migrate_vm_to_host(poolname, vmname, dest_host_name) }.to raise_error(/#{cluster_name} which does not exist/)
end
end
context 'Given a missing cluster name in the global configuration' do
let(:cluster_name) { 'missing_cluster' }
before(:each) do
config[:pools][0]['clone_target'] = nil
config[:config]['clone_target'] = cluster_name
expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(nil)
end
it 'should raise an error' do
expect{ subject.migrate_vm_to_host(poolname, vmname, dest_host_name) }.to raise_error(/#{cluster_name} which does not exist/)
end
end
context 'Given a missing hostname in the cluster' do
before(:each) do
config[:pools][0]['clone_target'] = cluster_name
mock_cluster = mock_RbVmomi_VIM_ComputeResource({
:hosts => [ { :name => 'HOST001' },{ :name => dest_host_name} ]
})
expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(mock_cluster)
expect(subject).to receive(:migrate_vm_host).exactly(0).times
end
it 'should return true' do
expect(subject.migrate_vm_to_host(poolname, vmname, 'missing_host')).to be false
end
end
context 'Given an error during migration' do
before(:each) do
config[:pools][0]['clone_target'] = cluster_name
mock_cluster = mock_RbVmomi_VIM_ComputeResource({
:hosts => [ { :name => 'HOST001' },{ :name => dest_host_name} ]
})
expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(mock_cluster)
expect(subject).to receive(:migrate_vm_host).with(Object,Object).and_raise(RuntimeError,'MockMigrationError')
end
it 'should raise an error' do
expect{ subject.migrate_vm_to_host(poolname, vmname, dest_host_name) }.to raise_error('MockMigrationError')
end
end
context 'Given a successful migration' do
before(:each) do
config[:pools][0]['clone_target'] = cluster_name
mock_cluster = mock_RbVmomi_VIM_ComputeResource({
:hosts => [ { :name => 'HOST001' },{ :name => dest_host_name} ]
})
expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(mock_cluster)
expect(subject).to receive(:migrate_vm_host).with(Object,Object).and_return(nil)
end
it 'should return true' do
expect(subject.migrate_vm_to_host(poolname, vmname, dest_host_name)).to be true
end
end
end
describe '#get_vm' do
let(:vm_object) { nil }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
expect(subject).to receive(:find_vm).with(vmname,connection).and_return(vm_object)
end
context 'when VM does not exist' do
it 'should return nil' do
expect(subject.get_vm(poolname,vmname)).to be_nil
end
end
context 'when VM exists but is missing information' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
}
it 'should return a hash' do
expect(subject.get_vm(poolname,vmname)).to be_kind_of(Hash)
end
it 'should return the VM name' do
result = subject.get_vm(poolname,vmname)
expect(result['name']).to eq(vmname)
end
['hostname','template','poolname','boottime','powerstate'].each do |testcase|
it "should return nil for #{testcase}" do
result = subject.get_vm(poolname,vmname)
expect(result[testcase]).to be_nil
end
end
end
context 'when VM exists and contains all information' do
let(:vm_hostname) { "#{vmname}.demo.local" }
let(:boot_time) { Time.now }
let(:power_state) { 'MockPowerState' }
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
:hostname => vm_hostname,
:powerstate => power_state,
:boottime => boot_time,
# This path should match the folder in the mocked pool in the config above
:path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'datacenter', :name => 'DC01' },
{ :type => 'folder', :name => 'vm' },
{ :type => 'folder', :name => 'Pooler' },
{ :type => 'folder', :name => 'pool1'},
]
})
}
let(:pool_info) { config[:pools][0]}
it 'should return a hash' do
expect(subject.get_vm(poolname,vmname)).to be_kind_of(Hash)
end
it 'should return the VM name' do
result = subject.get_vm(poolname,vmname)
expect(result['name']).to eq(vmname)
end
it 'should return the VM hostname' do
result = subject.get_vm(poolname,vmname)
expect(result['hostname']).to eq(vm_hostname)
end
it 'should return the template name' do
result = subject.get_vm(poolname,vmname)
expect(result['template']).to eq(pool_info['template'])
end
it 'should return the pool name' do
result = subject.get_vm(poolname,vmname)
expect(result['poolname']).to eq(pool_info['name'])
end
it 'should return the boot time' do
result = subject.get_vm(poolname,vmname)
expect(result['boottime']).to eq(boot_time)
end
it 'should return the powerstate' do
result = subject.get_vm(poolname,vmname)
expect(result['powerstate']).to eq(power_state)
end
end
end
describe '#create_vm' do
let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({
:datastores => ['datastore0'],
:vmfolder_tree => {
'Templates' => { :children => {
'pool1' => { :object_type => 'vm', :name => 'pool1' },
}},
'Pooler' => { :children => {
'pool1' => nil,
}},
},
:hostfolder_tree => {
'cluster1' => {:object_type => 'compute_resource'},
}
})
}
let(:clone_vm_task) { mock_RbVmomi_VIM_Task() }
let(:new_vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
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 pool name' do
it 'should raise an error' do
expect{ subject.create_vm('missing_pool', vmname) }.to raise_error(/missing_pool does not exist/)
end
end
context 'Given an invalid template path in the pool config' do
before(:each) do
config[:pools][0]['template'] = 'bad_template'
end
it 'should raise an error' do
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/did specify a full path for the template/)
end
end
context 'Given a template path that does not exist' do
before(:each) do
config[:pools][0]['template'] = 'missing_Templates/pool1'
end
it 'should raise an error' do
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/specifies a template folder of .+ which does not exist/)
end
end
context 'Given a template VM that does not exist' do
before(:each) do
config[:pools][0]['template'] = 'Templates/missing_template'
end
it 'should raise an error' do
expect{ subject.create_vm(poolname, vmname) }.to raise_error(/specifies a template VM of .+ which does not exist/)
end
end
context 'Given a successful creation' do
before(:each) do
template_vm = subject.find_folder('Templates',connection,datacenter_name).find('pool1')
allow(template_vm).to receive(:CloneVM_Task).and_return(clone_vm_task)
allow(clone_vm_task).to receive(:wait_for_completion).and_return(new_vm_object)
end
it 'should return a hash' do
result = subject.create_vm(poolname, vmname)
expect(result.is_a?(Hash)).to be true
end
it 'should use the appropriate Create_VM spec' do
template_vm = subject.find_folder('Templates',connection,datacenter_name).find('pool1')
expect(template_vm).to receive(:CloneVM_Task)
.with(create_vm_spec(vmname,'pool1','datastore0'))
.and_return(clone_vm_task)
subject.create_vm(poolname, vmname)
end
it 'should have the new VM name' do
result = subject.create_vm(poolname, vmname)
expect(result['name']).to eq(vmname)
end
end
end
describe '#create_disk' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }) }
let(:datastorename) { 'datastore0' }
let(:disk_size) { 10 }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
allow(subject).to receive(:find_vm).with(vmname, connection).and_return(vm_object)
end
context 'Given an invalid pool name' do
it 'should raise an error' do
expect{ subject.create_disk('missing_pool',vmname,disk_size) }.to raise_error(/missing_pool does not exist/)
end
end
context 'Given a missing datastore in the pool config' do
before(:each) do
config[:pools][0]['datastore'] = nil
end
it 'should raise an error' do
expect{ subject.create_disk(poolname,vmname,disk_size) }.to raise_error(/does not have a datastore defined/)
end
end
context 'when VM does not exist' do
before(:each) do
expect(subject).to receive(:find_vm).with(vmname, connection).and_return(nil)
end
it 'should raise an error' do
expect{ subject.create_disk(poolname,vmname,disk_size) }.to raise_error(/VM #{vmname} .+ does not exist/)
end
end
context 'when adding the disk raises an error' do
before(:each) do
expect(subject).to receive(:add_disk).and_raise(RuntimeError,'Mock Disk Error')
end
it 'should raise an error' do
expect{ subject.create_disk(poolname,vmname,disk_size) }.to raise_error(/Mock Disk Error/)
end
end
context 'when adding the disk succeeds' do
before(:each) do
expect(subject).to receive(:add_disk).with(vm_object, disk_size, datastorename, connection, datacenter_name)
end
it 'should return true' do
expect(subject.create_disk(poolname,vmname,disk_size)).to be true
end
end
end
describe '#create_snapshot' do
let(:snapshot_task) { mock_RbVmomi_VIM_Task() }
let(:snapshot_name) { 'snapshot' }
let(:snapshot_tree) {{}}
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname, :snapshot_tree => snapshot_tree }) }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
allow(subject).to receive(:find_vm).with(vmname,connection).and_return(vm_object)
end
context 'when VM does not exist' do
before(:each) do
expect(subject).to receive(:find_vm).with(vmname, connection).and_return(nil)
end
it 'should raise an error' do
expect{ subject.create_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/VM #{vmname} .+ does not exist/)
end
end
context 'when snapshot already exists' do
let(:snapshot_object) { mock_RbVmomi_VIM_VirtualMachineSnapshot() }
let(:snapshot_tree) { { snapshot_name => { :ref => snapshot_object } } }
it 'should raise an error' do
expect{ subject.create_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/Snapshot #{snapshot_name} .+ already exists /)
end
end
context 'when snapshot raises an error' do
before(:each) do
expect(vm_object).to receive(:CreateSnapshot_Task).and_raise(RuntimeError,'Mock Snapshot Error')
end
it 'should raise an error' do
expect{ subject.create_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/Mock Snapshot Error/)
end
end
context 'when snapshot succeeds' do
before(:each) do
expect(vm_object).to receive(:CreateSnapshot_Task)
.with(create_snapshot_spec(snapshot_name))
.and_return(snapshot_task)
expect(snapshot_task).to receive(:wait_for_completion).and_return(nil)
end
it 'should return true' do
expect(subject.create_snapshot(poolname,vmname,snapshot_name)).to be true
end
end
end
describe '#revert_snapshot' do
let(:snapshot_task) { mock_RbVmomi_VIM_Task() }
let(:snapshot_name) { 'snapshot' }
let(:snapshot_tree) { { snapshot_name => { :ref => snapshot_object } } }
let(:snapshot_object) { mock_RbVmomi_VIM_VirtualMachineSnapshot() }
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname, :snapshot_tree => snapshot_tree }) }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
allow(subject).to receive(:find_vm).with(vmname,connection).and_return(vm_object)
end
context 'when VM does not exist' do
before(:each) do
expect(subject).to receive(:find_vm).with(vmname,connection).and_return(nil)
end
it 'should raise an error' do
expect{ subject.revert_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/VM #{vmname} .+ does not exist/)
end
end
context 'when snapshot does not exist' do
let(:snapshot_tree) {{}}
it 'should raise an error' do
expect{ subject.revert_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/Snapshot #{snapshot_name} .+ does not exist /)
end
end
context 'when revert to snapshot raises an error' do
before(:each) do
expect(snapshot_object).to receive(:RevertToSnapshot_Task).and_raise(RuntimeError,'Mock Snapshot Error')
end
it 'should raise an error' do
expect{ subject.revert_snapshot(poolname,vmname,snapshot_name) }.to raise_error(/Mock Snapshot Error/)
end
end
context 'when revert to snapshot succeeds' do
before(:each) do
expect(snapshot_object).to receive(:RevertToSnapshot_Task).and_return(snapshot_task)
expect(snapshot_task).to receive(:wait_for_completion).and_return(nil)
end
it 'should return true' do
expect(subject.revert_snapshot(poolname,vmname,snapshot_name)).to be true
end
end
end
describe '#destroy_vm' do
let(:power_off_task) { mock_RbVmomi_VIM_Task() }
let(:destroy_task) { mock_RbVmomi_VIM_Task() }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection)
end
context 'Given a missing VM name' do
before(:each) do
expect(subject).to receive(:find_vm).and_return(nil)
end
it 'should return true' do
expect(subject.destroy_vm(poolname, 'missing_vm')).to be true
end
end
context 'Given a powered on VM' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
:powerstate => 'poweredOn',
})
}
before(:each) do
expect(subject).to receive(:find_vm).and_return(vm_object)
allow(vm_object).to receive(:PowerOffVM_Task).and_return(power_off_task)
allow(vm_object).to receive(:Destroy_Task).and_return(destroy_task)
allow(power_off_task).to receive(:wait_for_completion)
allow(destroy_task).to receive(:wait_for_completion)
end
it 'should call PowerOffVM_Task on the VM' do
expect(vm_object).to receive(:PowerOffVM_Task).and_return(power_off_task)
subject.destroy_vm(poolname, vmname)
end
it 'should call Destroy_Task on the VM' do
expect(vm_object).to receive(:Destroy_Task).and_return(destroy_task)
subject.destroy_vm(poolname, vmname)
end
it 'should return true' do
expect(subject.destroy_vm(poolname, vmname)).to be true
end
end
context 'Given a powered off VM' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
:powerstate => 'poweredOff',
})
}
before(:each) do
expect(subject).to receive(:find_vm).and_return(vm_object)
allow(vm_object).to receive(:Destroy_Task).and_return(destroy_task)
allow(destroy_task).to receive(:wait_for_completion)
end
it 'should not call PowerOffVM_Task on the VM' do
expect(vm_object).to receive(:PowerOffVM_Task).exactly(0).times
subject.destroy_vm(poolname, vmname)
end
it 'should call Destroy_Task on the VM' do
expect(vm_object).to receive(:Destroy_Task).and_return(destroy_task)
subject.destroy_vm(poolname, vmname)
end
it 'should return true' do
expect(subject.destroy_vm(poolname, vmname)).to be true
end
end
end
describe '#vm_ready?' do
context 'When a VM is ready' do
before(:each) do
expect(subject).to receive(:open_socket).with(vmname)
end
it 'should return true' do
expect(subject.vm_ready?(poolname,vmname)).to be true
end
end
context 'When a VM is ready but the pool does not exist' do
# TODO not sure how to handle a VM that is passed in but
# not located in the pool. Is that ready or not?
before(:each) do
expect(subject).to receive(:open_socket).with(vmname)
end
it 'should return true' do
expect(subject.vm_ready?('missing_pool',vmname)).to be true
end
end
context 'When an error occurs connecting to the VM' do
# TODO not sure how to handle a VM that is passed in but
# not located in the pool. Is that ready or not?
before(:each) do
expect(subject).to receive(:open_socket).and_raise(RuntimeError,'MockError')
end
it 'should return false' do
expect(subject.vm_ready?(poolname,vmname)).to be false
end
end
end
describe '#vm_exists?' do
it 'should return true when get_vm returns an object' do
allow(subject).to receive(:get_vm).with(poolname,vmname).and_return(mock_RbVmomi_VIM_VirtualMachine({ :name => vmname }))
expect(subject.vm_exists?(poolname,vmname)).to eq(true)
end
it 'should return false when get_vm returns nil' do
allow(subject).to receive(:get_vm).with(poolname,vmname).and_return(nil)
expect(subject.vm_exists?(poolname,vmname)).to eq(false)
end
end
# vSphere helper methods
describe '#get_target_datacenter_from_config' do
let(:pool_dc) { 'PoolDC'}
let(:provider_dc) { 'ProvDC'}
context 'when not specified' do
let(:config) { YAML.load(<<-EOT
---
:config:
:providers:
:vsphere:
server: "vcenter.domain.local"
username: "vcenter_user"
password: "vcenter_password"
:pools:
- name: '#{poolname}'
EOT
)
}
it 'returns nil' do
expect(subject.get_target_datacenter_from_config(poolname)).to be_nil
end
end
context 'when specified only in the pool' do
let(:config) { YAML.load(<<-EOT
---
:config:
:providers:
:vsphere:
server: "vcenter.domain.local"
username: "vcenter_user"
password: "vcenter_password"
:pools:
- name: '#{poolname}'
datacenter: '#{pool_dc}'
EOT
)
}
it 'returns the pool datacenter' do
expect(subject.get_target_datacenter_from_config(poolname)).to eq(pool_dc)
end
end
context 'when specified only in the provider' do
let(:config) { YAML.load(<<-EOT
---
:config:
:providers:
:vsphere:
server: "vcenter.domain.local"
username: "vcenter_user"
password: "vcenter_password"
datacenter: '#{provider_dc}'
:pools:
- name: '#{poolname}'
EOT
)
}
it 'returns the provider datacenter' do
expect(subject.get_target_datacenter_from_config(poolname)).to eq(provider_dc)
end
end
context 'when specified in the provider and pool' do
let(:config) { YAML.load(<<-EOT
---
:config:
:providers:
:vsphere:
server: "vcenter.domain.local"
username: "vcenter_user"
password: "vcenter_password"
datacenter: '#{provider_dc}'
:pools:
- name: '#{poolname}'
datacenter: '#{pool_dc}'
EOT
)
}
it 'returns the pool datacenter' do
expect(subject.get_target_datacenter_from_config(poolname)).to eq(pool_dc)
end
end
end
# vSphere helper methods
describe '#ensured_vsphere_connection' do
let(:config) { YAML.load(<<-EOT
---
:config:
:providers:
:vsphere:
# Drop the connection pool timeout way down for spec tests so they fail fast
connection_pool_timeout: 1
connection_pool_size: 1
:pools:
EOT
)
}
let(:connection1) { mock_RbVmomi_VIM_Connection(connection_options) }
let(:connection2) { mock_RbVmomi_VIM_Connection(connection_options) }
before(:each) do
allow(subject).to receive(:connect_to_vsphere).and_return(connection1)
end
# This is to ensure that the pool_size of 1 is in effect
it 'should return the same connection object when calling the pool multiple times' do
subject.connection_pool.with_metrics do |pool_object|
expect(pool_object[:connection]).to be(connection1)
end
subject.connection_pool.with_metrics do |pool_object|
expect(pool_object[:connection]).to be(connection1)
end
subject.connection_pool.with_metrics do |pool_object|
expect(pool_object[:connection]).to be(connection1)
end
end
context 'when the connection breaks' do
before(:each) do
# Emulate the connection state being good, then bad, then good again
expect(subject).to receive(:vsphere_connection_ok?).and_return(true, false, true)
expect(subject).to receive(:connect_to_vsphere).and_return(connection1, connection2)
end
it 'should restore the connection' do
subject.connection_pool.with_metrics do |pool_object|
# This line needs to be added to all instances of the connection_pool allocation
connection = subject.ensured_vsphere_connection(pool_object)
expect(connection).to be(connection1)
end
subject.connection_pool.with_metrics do |pool_object|
connection = subject.ensured_vsphere_connection(pool_object)
# The second connection would have failed. This test ensures that a
# new connection object was created.
expect(connection).to be(connection2)
end
subject.connection_pool.with_metrics do |pool_object|
connection = subject.ensured_vsphere_connection(pool_object)
expect(connection).to be(connection2)
end
end
end
end
describe '#connect_to_vsphere' do
before(:each) do
allow(RbVmomi::VIM).to receive(:connect).and_return(connection)
end
let (:credentials) { config[:providers][:vsphere] }
context 'succesful connection' do
it 'should use the supplied credentials' do
expect(RbVmomi::VIM).to receive(:connect).with({
:host => credentials['server'],
:user => credentials['username'],
:password => credentials['password'],
:insecure => credentials['insecure']
}).and_return(connection)
subject.connect_to_vsphere
end
it 'should honor the insecure setting' do
pending('Resolution of issue https://github.com/puppetlabs/vmpooler/issues/207')
config[:vsphere][:insecure] = false
expect(RbVmomi::VIM).to receive(:connect).with({
:host => credentials['server'],
:user => credentials['username'],
:password => credentials['password'],
:insecure => false,
}).and_return(connection)
subject.connect_to_vsphere
end
it 'should default to an insecure connection' do
config[:providers][:vsphere][:insecure] = nil
expect(RbVmomi::VIM).to receive(:connect).with({
:host => credentials['server'],
:user => credentials['username'],
:password => credentials['password'],
:insecure => true
}).and_return(connection)
subject.connect_to_vsphere
end
it 'should return the connection object' do
result = subject.connect_to_vsphere
expect(result).to be(connection)
end
it 'should increment the connect.open counter' do
expect(metrics).to receive(:increment).with('connect.open')
subject.connect_to_vsphere
end
end
context 'connection is initially unsuccessful' do
before(:each) do
# Simulate a failure and then success
expect(RbVmomi::VIM).to receive(:connect).and_raise(RuntimeError,'MockError').ordered
expect(RbVmomi::VIM).to receive(:connect).and_return(connection).ordered
allow(subject).to receive(:sleep)
end
it 'should return the connection object' do
result = subject.connect_to_vsphere
expect(result).to be(connection)
end
it 'should increment the connect.fail and then connect.open counter' do
expect(metrics).to receive(:increment).with('connect.fail').exactly(1).times
expect(metrics).to receive(:increment).with('connect.open').exactly(1).times
subject.connect_to_vsphere
end
end
context 'connection is always unsuccessful' do
before(:each) do
allow(RbVmomi::VIM).to receive(:connect).and_raise(RuntimeError,'MockError')
allow(subject).to receive(:sleep)
end
it 'should raise an error' do
expect{subject.connect_to_vsphere}.to raise_error(RuntimeError,'MockError')
end
it 'should retry the connection attempt config.max_tries times' do
pending('Resolution of issue https://github.com/puppetlabs/vmpooler/issues/199')
expect(RbVmomi::VIM).to receive(:connect).exactly(config[:config]['max_tries']).times.and_raise(RuntimeError,'MockError')
begin
# Swallow any errors
subject.connect_to_vsphere
rescue
end
end
it 'should increment the connect.fail counter config.max_tries times' do
pending('Resolution of issue https://github.com/puppetlabs/vmpooler/issues/199')
expect(metrics).to receive(:increment).with('connect.fail').exactly(config[:config]['max_tries']).times
begin
# Swallow any errors
subject.connect_to_vsphere
rescue
end
end
[{:max_tries => 5, :retry_factor => 1},
{:max_tries => 8, :retry_factor => 5},
].each do |testcase|
context "Configuration set for max_tries of #{testcase[:max_tries]} and retry_facter of #{testcase[:retry_factor]}" do
it "should sleep #{testcase[:max_tries] - 1} times between attempts with increasing timeout" do
pending('Resolution of issue https://github.com/puppetlabs/vmpooler/issues/199')
config[:config]['max_tries'] = testcase[:max_tries]
config[:config]['retry_factor'] = testcase[:retry_factor]
(1..testcase[:max_tries] - 1).each do |try|
expect(subject).to receive(:sleep).with(testcase[:retry_factor] * try).ordered
end
begin
# Swallow any errors
subject.connect_to_vsphere
rescue
end
end
end
end
end
end
describe '#open_socket' do
let(:TCPSocket) { double('tcpsocket') }
let(:socket) { double('tcpsocket') }
let(:hostname) { 'host' }
let(:domain) { 'domain.local'}
let(:default_socket) { 22 }
before do
expect(subject).not_to be_nil
allow(socket).to receive(:close)
end
it 'opens socket with defaults' do
expect(TCPSocket).to receive(:new).with(hostname,default_socket).and_return(socket)
expect(subject.open_socket(hostname)).to eq(nil)
end
it 'yields the socket if a block is given' do
expect(TCPSocket).to receive(:new).with(hostname,default_socket).and_return(socket)
expect{ |socket| subject.open_socket(hostname,nil,nil,default_socket,&socket) }.to yield_control.exactly(1).times
end
it 'closes the opened socket' do
expect(TCPSocket).to receive(:new).with(hostname,default_socket).and_return(socket)
expect(socket).to receive(:close)
expect(subject.open_socket(hostname)).to eq(nil)
end
it 'opens a specific socket' do
expect(TCPSocket).to receive(:new).with(hostname,80).and_return(socket)
expect(subject.open_socket(hostname,nil,nil,80)).to eq(nil)
end
it 'uses a specific domain with the hostname' do
expect(TCPSocket).to receive(:new).with("#{hostname}.#{domain}",default_socket).and_return(socket)
expect(subject.open_socket(hostname,domain)).to eq(nil)
end
it 'raises error if host is not resolvable' do
expect(TCPSocket).to receive(:new).with(hostname,default_socket).and_raise(SocketError,'getaddrinfo: No such host is known')
expect { subject.open_socket(hostname,nil,1) }.to raise_error(SocketError)
end
it 'raises error if socket is not listening' do
expect(TCPSocket).to receive(:new).with(hostname,default_socket).and_raise(SocketError,'No connection could be made because the target machine actively refused it')
expect { subject.open_socket(hostname,nil,1) }.to raise_error(SocketError)
end
end
describe '#get_vm_folder_path' do
[
{ :path_description => 'Datacenters/DC01/vm/Pooler/pool1/vm1',
:expected_path => 'Pooler/pool1',
:vm_object_path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'datacenter', :name => 'DC01' },
{ :type => 'folder', :name => 'vm' },
{ :type => 'folder', :name => 'Pooler' },
{ :type => 'folder', :name => 'pool1'},
],
},
{ :path_description => 'Datacenters/DC01/vm/something/subfolder/pool/vm1',
:expected_path => 'something/subfolder/pool',
:vm_object_path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'datacenter', :name => 'DC01' },
{ :type => 'folder', :name => 'vm' },
{ :type => 'folder', :name => 'something' },
{ :type => 'folder', :name => 'subfolder' },
{ :type => 'folder', :name => 'pool'},
],
},
{ :path_description => 'Datacenters/DC01/vm/vm1',
:expected_path => '',
:vm_object_path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'datacenter', :name => 'DC01' },
{ :type => 'folder', :name => 'vm' },
],
},
].each do |testcase|
context "given a path of #{testcase[:path_description]}" do
it "should return '#{testcase[:expected_path]}'" do
vm_object = mock_RbVmomi_VIM_VirtualMachine({
:name => 'vm1',
:path => testcase[:vm_object_path],
})
expect(subject.get_vm_folder_path(vm_object)).to eq(testcase[:expected_path])
end
end
end
[
{ :path_description => 'a path missing a Datacenter',
:vm_object_path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'folder', :name => 'vm' },
{ :type => 'folder', :name => 'Pooler' },
{ :type => 'folder', :name => 'pool1'},
],
},
{ :path_description => 'a path missing root VM folder',
:vm_object_path => [
{ :type => 'folder', :name => 'Datacenters' },
{ :type => 'datacenter', :name => 'DC01' },
],
},
].each do |testcase|
context "given #{testcase[:path_description]}" do
it "should return nil" do
vm_object = mock_RbVmomi_VIM_VirtualMachine({
:name => 'vm1',
:path => testcase[:vm_object_path],
})
expect(subject.get_vm_folder_path(vm_object)).to be_nil
end
end
end
end
describe '#add_disk' do
let(:datastorename) { 'datastore' }
let(:disk_size) { 30 }
let(:collectMultiple_response) { {} }
let(:vm_scsi_controller) { mock_RbVmomi_VIM_VirtualSCSIController() }
# Require at least one SCSI Controller
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
mock_vm.config.hardware.device << vm_scsi_controller
mock_vm
}
# Require at least one DC with the required datastore
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name, :datastores => [datastorename] }
]
}
}}
let(:create_virtual_disk_task) { mock_RbVmomi_VIM_Task() }
let(:reconfig_vm_task) { mock_RbVmomi_VIM_Task() }
before(:each) do
# Mocking for find_vmdks
allow(connection.serviceContent.propertyCollector).to receive(:collectMultiple).and_return(collectMultiple_response)
# Mocking for creating the disk
allow(connection.serviceContent.virtualDiskManager).to receive(:CreateVirtualDisk_Task) do |options|
if options[:datacenter][:name] == datacenter_name
create_virtual_disk_task
end
end
allow(create_virtual_disk_task).to receive(:wait_for_completion).and_return(true)
# Mocking for adding disk to the VM
allow(vm_object).to receive(:ReconfigVM_Task).and_return(reconfig_vm_task)
allow(reconfig_vm_task).to receive(:wait_for_completion).and_return(true)
end
context 'Succesfully addding disk' do
it 'should return true' do
expect(subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name)).to be true
end
it 'should request a disk of appropriate size' do
expect(connection.serviceContent.virtualDiskManager).to receive(:CreateVirtualDisk_Task)
.with(create_virtual_disk_with_size(disk_size))
.and_return(create_virtual_disk_task)
subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name)
end
end
context 'Requested disk size is 0' do
it 'should raise an error' do
expect(subject.add_disk(vm_object,0,datastorename,connection,datacenter_name)).to be false
end
end
context 'No datastores or datastore missing' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name, :datastores => ['missing_datastore'] }
]
}
}}
it 'should raise error' do
expect{ subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name) }.to raise_error(/does not exist/)
end
end
context 'Multiple datacenters with multiple datastores' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => 'AnotherDC', :datastores => ['dc1','dc2'] },
{ :name => datacenter_name, :datastores => ['dc3',datastorename,'dc4'] },
]
}
}}
it 'should return true' do
expect(subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name)).to be true
end
end
context 'VM does not have a SCSI Controller' do
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine({
:name => vmname,
})
mock_vm
}
it 'should raise an error' do
expect{ subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name) }.to raise_error(NoMethodError)
end
end
end
describe '#find_datastore' do
let(:datastorename) { 'datastore' }
let(:datastore_list) { [] }
context 'No datastores in the datacenter' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name, :datastores => [] }
]
}
}}
it 'should return nil if the datastore is not found' do
result = subject.find_datastore(datastorename,connection,datacenter_name)
expect(result).to be_nil
end
end
context 'Many datastores in the datacenter' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name, :datastores => ['ds1','ds2',datastorename,'ds3'] }
]
}
}}
it 'should return nil if the datastore is not found' do
result = subject.find_datastore('missing_datastore',connection,datacenter_name)
expect(result).to be_nil
end
it 'should find the datastore in the datacenter' do
result = subject.find_datastore(datastorename,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.is_a?(RbVmomi::VIM::Datastore)).to be true
expect(result.name).to eq(datastorename)
end
end
context 'Many datastores in many datacenters' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => 'AnotherDC', :datastores => ['ds1','ds2','ds3'] },
{ :name => datacenter_name, :datastores => ['ds3','ds4',datastorename,'ds5'] },
]
}
}}
it 'should return nil if the datastore is not found' do
result = subject.find_datastore(datastorename,connection,'AnotherDC')
expect(result).to be_nil
end
it 'should find the datastore in the datacenter' do
result = subject.find_datastore(datastorename,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.is_a?(RbVmomi::VIM::Datastore)).to be true
expect(result.name).to eq(datastorename)
end
end
end
describe '#find_device' do
let(:devicename) { 'device1' }
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine()
mock_vm.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
mock_vm.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device2'})
mock_vm
}
it 'should return a device if the device name matches' do
result = subject.find_device(vm_object,devicename)
expect(result.deviceInfo.label).to eq(devicename)
end
it 'should return nil if the device name does not match' do
result = subject.find_device(vm_object,'missing_device')
expect(result).to be_nil
end
end
describe '#find_disk_controller' do
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine()
mock_vm
}
it 'should return nil when there are no devices' do
result = subject.find_disk_controller(vm_object)
expect(result).to be_nil
end
[0,1,14].each do |testcase|
it "should return a device for a single VirtualSCSIController with #{testcase} attached disks" do
mock_scsi = mock_RbVmomi_VIM_VirtualSCSIController()
vm_object.config.hardware.device << mock_scsi
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device2'})
# Add the disks
(1..testcase).each do
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualDisk({ :controllerKey => mock_scsi.key })
end
result = subject.find_disk_controller(vm_object)
expect(result).to eq(mock_scsi)
end
end
[15].each do |testcase|
it "should return nil for a single VirtualSCSIController with #{testcase} attached disks" do
mock_scsi = mock_RbVmomi_VIM_VirtualSCSIController()
vm_object.config.hardware.device << mock_scsi
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device2'})
# Add the disks
(1..testcase).each do
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualDisk({ :controllerKey => mock_scsi.key })
end
result = subject.find_disk_controller(vm_object)
expect(result).to be_nil
end
end
it 'should raise if a VirtualDisk is missing a controller' do
# Note - Typically this is not possible as a VirtualDisk requires a controller (SCSI, PVSCSI or IDE)
mock_scsi = mock_RbVmomi_VIM_VirtualDisk()
vm_object.config.hardware.device << mock_scsi
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device2'})
expect{subject.find_disk_controller(vm_object)}.to raise_error(NoMethodError)
end
end
describe '#find_disk_devices' do
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine()
mock_vm
}
it 'should return empty hash when there are no devices' do
result = subject.find_disk_devices(vm_object)
expect(result).to eq({})
end
it 'should return empty hash when there are no VirtualSCSIController or VirtualDisk devices' do
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device2'})
result = subject.find_disk_devices(vm_object)
expect(result).to eq({})
end
it 'should return a device for a VirtualSCSIController device with no children' do
mock_scsi = mock_RbVmomi_VIM_VirtualSCSIController()
vm_object.config.hardware.device << mock_scsi
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
result = subject.find_disk_devices(vm_object)
expect(result.count).to eq(1)
expect(result[mock_scsi.key]).to_not be_nil
expect(result[mock_scsi.key]['children']).to eq([])
expect(result[mock_scsi.key]['device']).to eq(mock_scsi)
end
it 'should return a device for a VirtualDisk device' do
mock_disk = mock_RbVmomi_VIM_VirtualDisk()
vm_object.config.hardware.device << mock_disk
vm_object.config.hardware.device << mock_RbVmomi_VIM_VirtualMachineDevice({:label => 'device1'})
result = subject.find_disk_devices(vm_object)
expect(result.count).to eq(1)
expect(result[mock_disk.controllerKey]).to_not be_nil
expect(result[mock_disk.controllerKey]['children'][0]).to eq(mock_disk)
end
it 'should return one device for many VirtualDisk devices on the same controller' do
controller1Key = rand(2000)
controller2Key = controller1Key + 1
mock_disk1 = mock_RbVmomi_VIM_VirtualDisk({:controllerKey => controller1Key})
mock_disk2 = mock_RbVmomi_VIM_VirtualDisk({:controllerKey => controller1Key})
mock_disk3 = mock_RbVmomi_VIM_VirtualDisk({:controllerKey => controller2Key})
vm_object.config.hardware.device << mock_disk2
vm_object.config.hardware.device << mock_disk1
vm_object.config.hardware.device << mock_disk3
result = subject.find_disk_devices(vm_object)
expect(result.count).to eq(2)
expect(result[controller1Key]).to_not be_nil
expect(result[controller2Key]).to_not be_nil
expect(result[controller1Key]['children']).to contain_exactly(mock_disk1,mock_disk2)
expect(result[controller2Key]['children']).to contain_exactly(mock_disk3)
end
end
describe '#find_disk_unit_number' do
let(:vm_object) {
mock_vm = mock_RbVmomi_VIM_VirtualMachine()
mock_vm
}
let(:controller) { mock_RbVmomi_VIM_VirtualSCSIController() }
it 'should return 0 when there are no devices' do
result = subject.find_disk_unit_number(vm_object,controller)
expect(result).to eq(0)
end
context 'with a single SCSI Controller' do
before(:each) do
vm_object.config.hardware.device << controller
end
it 'should return 1 when the host bus controller is at 0' do
controller.scsiCtlrUnitNumber = 0
result = subject.find_disk_unit_number(vm_object,controller)
expect(result).to eq(1)
end
it 'should return the next lowest id when disks are attached' do
expected_id = 9
controller.scsiCtlrUnitNumber = 0
(1..expected_id-1).each do |disk_id|
mock_disk = mock_RbVmomi_VIM_VirtualDisk({
:controllerKey => controller.key,
:unitNumber => disk_id,
})
vm_object.config.hardware.device << mock_disk
end
result = subject.find_disk_unit_number(vm_object,controller)
expect(result).to eq(expected_id)
end
it 'should return nil when there are no spare units' do
controller.scsiCtlrUnitNumber = 0
(1..15).each do |disk_id|
mock_disk = mock_RbVmomi_VIM_VirtualDisk({
:controllerKey => controller.key,
:unitNumber => disk_id,
})
vm_object.config.hardware.device << mock_disk
end
result = subject.find_disk_unit_number(vm_object,controller)
expect(result).to eq(nil)
end
end
end
describe '#find_folder' do
let(:foldername) { 'folder'}
let(:missing_foldername) { 'missing_folder'}
context 'with no folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name }
]
}
}}
it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil
end
end
context 'with a single layer folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => nil,
foldername => nil,
'folder3' => nil,
}
}
]
}
}}
it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(foldername)
end
it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil
end
end
context 'with a single layer folder hierarchy in many datacenters' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => 'AnotherDC',
:vmfolder_tree => {
'folder1' => nil,
'folder2' => nil,
'folder3' => nil,
}
},
{ :name => datacenter_name,
:vmfolder_tree => {
'folder4' => nil,
'folder5' => nil,
foldername => nil,
'folder6' => nil,
}
}
]
}
}}
it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(foldername)
end
it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,'AnotherDC')).to be_nil
end
end
context 'with a VM with the same name as a folder in a single layer folder hierarchy' do
# The folder hierarchy should include a VM with same name as folder, and appear BEFORE the
# folder in the child list.
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'vm1' => { :object_type => 'vm', :name => foldername },
foldername => nil,
'folder3' => nil,
}
}
]
}
}}
it 'should not return a VM' do
pending('https://github.com/puppetlabs/vmpooler/issues/204')
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(foldername)
expect(result.is_a? RbVmomi::VIM::VirtualMachine).to be false
end
end
context 'with a multi layer folder hierarchy' do
let(:end_folder_name) { 'folder'}
let(:foldername) { 'folder2/folder4/' + end_folder_name}
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => {
:children => {
'folder3' => nil,
'folder4' => {
:children => {
end_folder_name => nil,
},
}
},
},
'folder5' => nil,
}
}
]
}
}}
it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(end_folder_name)
end
it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil
end
end
context 'with a VM with the same name as a folder in a multi layer folder hierarchy' do
# The folder hierarchy should include a VM with same name as folder mid-hierarchy (i.e. not at the end level)
# and appear BEFORE the folder in the child list.
let(:end_folder_name) { 'folder'}
let(:foldername) { 'folder2/folder4/' + end_folder_name}
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => {
:children => {
'folder3' => nil,
'vm1' => { :object_type => 'vm', :name => 'folder4' },
'folder4' => {
:children => {
end_folder_name => nil,
},
}
},
},
'folder5' => nil,
}
}
]
}
}}
it 'should not return a VM' do
pending('https://github.com/puppetlabs/vmpooler/issues/204')
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(foldername)
expect(result.is_a? RbVmomi::VIM::VirtualMachine,datacenter_name).to be false
end
end
end
describe '#get_host_utilization' do
let(:cpu_model) { 'vendor line type sku v4 speed' }
let(:model) { 'v4' }
let(:different_model) { 'different_model' }
let(:limit) { 80 }
let(:default_limit) { 90 }
context "host with a different model" do
let(:host) { mock_RbVmomi_VIM_HostSystem() }
it 'should return nil' do
expect(subject.get_host_utilization(host,different_model,limit)).to be_nil
end
end
context "host in maintenance mode" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:maintenance_mode => true,
})
}
it 'should return nil' do
host.runtime.inMaintenanceMode = true
expect(subject.get_host_utilization(host,model,limit)).to be_nil
end
end
context "host with status of not green" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_status => 'purple_alert',
})
}
it 'should return nil' do
expect(subject.get_host_utilization(host,model,limit)).to be_nil
end
end
# CPU utilization
context "host which exceeds limit in CPU utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => 100,
:overall_memory_usage => 1,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should return nil' do
expect(subject.get_host_utilization(host,model,limit)).to be_nil
end
end
context "host which exceeds default limit in CPU utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => default_limit + 1.0,
:overall_memory_usage => 1,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should return nil' do
expect(subject.get_host_utilization(host,model)).to be_nil
end
end
context "host which does not exceed default limit in CPU utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => default_limit,
:overall_memory_usage => 1,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should not return nil' do
expect(subject.get_host_utilization(host,model)).to_not be_nil
end
end
# Memory utilization
context "host which exceeds limit in Memory utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => 1,
:overall_memory_usage => 100,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should return nil' do
# Set the Memory Usage to 100%
expect(subject.get_host_utilization(host,model,limit)).to be_nil
end
end
context "host which exceeds default limit in Memory utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => 1,
:overall_memory_usage => default_limit + 1.0,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should return nil' do
expect(subject.get_host_utilization(host,model)).to be_nil
end
end
context "host which does not exceed default limit in Memory utilization" do
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => 1,
:overall_memory_usage => default_limit,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should not return nil' do
expect(subject.get_host_utilization(host,model)).to_not be_nil
end
end
context "host which does not exceed limits" do
# Set CPU to 10%
# Set Memory to 20%
let(:host) { mock_RbVmomi_VIM_HostSystem({
:overall_cpu_usage => 10,
:overall_memory_usage => 20,
:cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
})
}
it 'should return the sum of CPU and Memory utilization' do
expect(subject.get_host_utilization(host,model,limit)[0]).to eq(10 + 20)
end
it 'should return the host' do
expect(subject.get_host_utilization(host,model,limit)[1]).to eq(host)
end
end
end
describe '#host_has_cpu_model?' do
let(:cpu_model) { 'vendor line type sku v4 speed' }
let(:model) { 'v4' }
let(:different_model) { 'different_model' }
let(:host) { mock_RbVmomi_VIM_HostSystem({
:cpu_model => cpu_model,
})
}
it 'should return true if the model matches' do
expect(subject.host_has_cpu_model?(host,model)).to eq(true)
end
it 'should return false if the model is different' do
expect(subject.host_has_cpu_model?(host,different_model)).to eq(false)
end
end
describe '#get_host_cpu_arch_version' do
let(:cpu_model) { 'vendor line type sku v4 speed' }
let(:model) { 'v4' }
let(:different_model) { 'different_model' }
let(:host) { mock_RbVmomi_VIM_HostSystem({
:cpu_model => cpu_model,
:num_cpu => 2,
})
}
it 'should return the fifth element in the string delimited by spaces' do
expect(subject.get_host_cpu_arch_version(host)).to eq(model)
end
it 'should use the description of the first CPU' do
host.hardware.cpuPkg[0].description = 'vendor line type sku v6 speed'
expect(subject.get_host_cpu_arch_version(host)).to eq('v6')
end
end
describe '#cpu_utilization_for' do
[{ :cpu_usage => 10.0,
:core_speed => 10.0,
:num_cores => 2,
:expected_value => 50.0,
},
{ :cpu_usage => 10.0,
:core_speed => 10.0,
:num_cores => 4,
:expected_value => 25.0,
},
{ :cpu_usage => 14.0,
:core_speed => 12.0,
:num_cores => 5,
:expected_value => 23.0 + 1.0/3.0,
},
].each do |testcase|
context "CPU Usage of #{testcase[:cpu_usage]}MHz with #{testcase[:num_cores]} x #{testcase[:core_speed]}MHz cores" do
it "should be #{testcase[:expected_value]}%" do
host = mock_RbVmomi_VIM_HostSystem({
:num_cores_per_cpu => testcase[:num_cores],
:cpu_speed => testcase[:core_speed],
:overall_cpu_usage => testcase[:cpu_usage],
})
expect(subject.cpu_utilization_for(host)).to eq(testcase[:expected_value])
end
end
end
end
describe '#memory_utilization_for' do
[{ :memory_usage_gigbytes => 10.0,
:memory_size_bytes => 10.0 * 1024 * 1024,
:expected_value => 100.0,
},
{ :memory_usage_gigbytes => 15.0,
:memory_size_bytes => 25.0 * 1024 * 1024,
:expected_value => 60.0,
},
{ :memory_usage_gigbytes => 9.0,
:memory_size_bytes => 31.0 * 1024 * 1024,
:expected_value => 29.03225806451613,
},
].each do |testcase|
context "Memory Usage of #{testcase[:memory_usage_gigbytes]}GBytes with #{testcase[:memory_size_bytes]}Bytes of total memory" do
it "should be #{testcase[:expected_value]}%" do
host = mock_RbVmomi_VIM_HostSystem({
:memory_size => testcase[:memory_size_bytes],
:overall_memory_usage => testcase[:memory_usage_gigbytes],
})
expect(subject.memory_utilization_for(host)).to eq(testcase[:expected_value])
end
end
end
end
describe '#find_least_used_host' do
let(:cluster_name) { 'cluster' }
let(:missing_cluster_name) { 'missing_cluster' }
let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter() }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
allow(connection.serviceInstance).to receive(:find_datacenter).and_return(datacenter_object)
datacenter_object.hostFolder.childEntity = [cluster_object]
end
context 'missing cluster' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [{
:name => cluster_name,
}]})}
let(:expected_host) { cluster_object.host[0] }
#,datacenter_name
it 'should raise an error' do
expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/)
end
end
context 'standalone host within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [{
:name => cluster_name,
}]})}
let(:expected_host) { cluster_object.host[0] }
it 'should return the standalone host' do
result = subject.find_least_used_host(cluster_name,connection,datacenter_name)
expect(result).to be(expected_host)
end
end
context 'standalone host outside the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [{
:name => cluster_name,
:overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024,
}]})}
let(:expected_host) { cluster_object.host[0] }
it 'should raise an error' do
expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/)
end
end
context 'cluster of 3 hosts within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
it 'should return the standalone host' do
result = subject.find_least_used_host(cluster_name,connection,datacenter_name)
expect(result).to be(expected_host)
end
end
context 'cluster of 3 hosts all outside of the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
it 'should raise an error' do
expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/)
end
end
context 'cluster of 5 hosts of which one is out of limits and one has wrong CPU type' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [
{ :overall_cpu_usage => 31, :overall_memory_usage => 31, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :cpu_model => 'different cpu model', :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
it 'should return the standalone host' do
result = subject.find_least_used_host(cluster_name,connection,datacenter_name)
expect(result).to be(expected_host)
end
end
context 'cluster of 3 hosts all outside of the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({
:name => cluster_name,
:hosts => [
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
it 'should return a host' do
pending('https://github.com/puppetlabs/vmpooler/issues/206')
result = subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)
expect(result).to_not be_nil
end
end
end
describe '#find_cluster' do
let(:cluster) {'cluster'}
let(:missing_cluster) {'missing_cluster'}
context 'no clusters in the datacenter' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name }
]
}
}}
it 'should return nil if the cluster is not found' do
expect(subject.find_cluster(missing_cluster,connection,datacenter_name)).to be_nil
end
end
context 'with a single layer folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:hostfolder_tree => {
'cluster1' => {:object_type => 'compute_resource'},
'cluster2' => {:object_type => 'compute_resource'},
cluster => {:object_type => 'compute_resource'},
'cluster3' => {:object_type => 'compute_resource'},
}
}
]
}
}}
it 'should return the cluster when found' do
result = subject.find_cluster(cluster,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(cluster)
end
it 'should return nil if the cluster is not found' do
expect(subject.find_cluster(missing_cluster,connection,datacenter_name)).to be_nil
end
end
context 'with a single layer folder hierarchy with multiple datacenters' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => 'AnotherDC',
:hostfolder_tree => {
'cluster1' => {:object_type => 'compute_resource'},
'cluster2' => {:object_type => 'compute_resource'},
}
},
{ :name => datacenter_name,
:hostfolder_tree => {
cluster => {:object_type => 'compute_resource'},
'cluster3' => {:object_type => 'compute_resource'},
}
}
]
}
}}
it 'should return the cluster when found' do
result = subject.find_cluster(cluster,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(cluster)
end
it 'should return nil if the cluster is not found' do
expect(subject.find_cluster(missing_cluster,connection,'AnotherDC')).to be_nil
end
end
context 'with a multi layer folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:hostfolder_tree => {
'cluster1' => {:object_type => 'compute_resource'},
'folder2' => {
:children => {
cluster => {:object_type => 'compute_resource'},
}
},
'cluster3' => {:object_type => 'compute_resource'},
}
}
]
}
}}
it 'should return the cluster when found' do
pending('https://github.com/puppetlabs/vmpooler/issues/205')
result = subject.find_cluster(cluster,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(cluster)
end
it 'should return nil if the cluster is not found' do
expect(subject.find_cluster(missing_cluster,connection,datacenter_name)).to be_nil
end
end
end
describe '#get_cluster_host_utilization' do
context 'standalone host within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [{}]}) }
it 'should return array with one element' do
result = subject.get_cluster_host_utilization(cluster_object)
expect(result).to_not be_nil
expect(result.count).to eq(1)
end
end
context 'standalone host which is out the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
it 'should return array with 0 elements' do
result = subject.get_cluster_host_utilization(cluster_object)
expect(result).to_not be_nil
expect(result.count).to eq(0)
end
end
context 'cluster with 3 hosts within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
it 'should return array with 3 elements' do
result = subject.get_cluster_host_utilization(cluster_object)
expect(result).to_not be_nil
expect(result.count).to eq(3)
end
end
context 'cluster with 5 hosts of which 3 within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
it 'should return array with 3 elements' do
result = subject.get_cluster_host_utilization(cluster_object)
expect(result).to_not be_nil
expect(result.count).to eq(3)
end
end
context 'cluster with 3 hosts of which none are within the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
it 'should return array with 0 elements' do
result = subject.get_cluster_host_utilization(cluster_object)
expect(result).to_not be_nil
expect(result.count).to eq(0)
end
end
end
describe '#find_least_used_vpshere_compatible_host' do
let(:vm) { mock_RbVmomi_VIM_VirtualMachine() }
context 'standalone host within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [{}]}) }
let(:standalone_host) { cluster_object.host[0] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = standalone_host
end
it 'should return the standalone host' do
result = subject.find_least_used_vpshere_compatible_host(vm)
expect(result).to_not be_nil
expect(result[0]).to be(standalone_host)
expect(result[1]).to eq(standalone_host.name)
end
end
context 'standalone host outside of limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:standalone_host) { cluster_object.host[0] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = standalone_host
end
it 'should raise error' do
expect{subject.find_least_used_vpshere_compatible_host(vm)}.to raise_error(/There is no host candidate in vcenter that meets all the required conditions/)
end
end
context 'cluster of 3 hosts within limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = expected_host
end
it 'should return the least used host' do
result = subject.find_least_used_vpshere_compatible_host(vm)
expect(result).to_not be_nil
expect(result[0]).to be(expected_host)
expect(result[1]).to eq(expected_host.name)
end
end
context 'cluster of 3 hosts all outside of the limits' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = expected_host
end
it 'should raise error' do
expect{subject.find_least_used_vpshere_compatible_host(vm)}.to raise_error(/There is no host candidate in vcenter that meets all the required conditions/)
end
end
context 'cluster of 5 hosts of which one is out of limits and one has wrong CPU type' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 31, :overall_memory_usage => 31, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :cpu_model => 'different cpu model', :overall_cpu_usage => 1, :overall_memory_usage => 1, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 11, :overall_memory_usage => 11, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 100, :overall_memory_usage => 100, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 21, :overall_memory_usage => 21, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[2] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = expected_host
end
it 'should return the least used host' do
result = subject.find_least_used_vpshere_compatible_host(vm)
expect(result).to_not be_nil
expect(result[0]).to be(expected_host)
expect(result[1]).to eq(expected_host.name)
end
end
context 'cluster of 3 hosts all with the same utilisation' do
let(:cluster_object) { mock_RbVmomi_VIM_ComputeResource({:hosts => [
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
{ :overall_cpu_usage => 10, :overall_memory_usage => 10, :cpu_speed => 100, :num_cores_per_cpu => 1, :num_cpu => 1, :memory_size => 100.0 * 1024 * 1024 },
]}) }
let(:expected_host) { cluster_object.host[1] }
before(:each) do
# This mocking is a little fragile but hard to do without a real vCenter instance
vm.summary.runtime.host = expected_host
end
it 'should return a host' do
pending('https://github.com/puppetlabs/vmpooler/issues/206 is fixed')
result = subject.find_least_used_vpshere_compatible_host(vm)
expect(result).to_not be_nil
end
end
end
describe '#find_pool' do
let(:poolname) { 'pool'}
let(:missing_poolname) { 'missing_pool'}
context 'with empty folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name }
]
}
}}
it 'should ensure the connection' do
pending('https://github.com/puppetlabs/vmpooler/issues/209')
expect(subject).to receive(:ensure_connected)
subject.find_pool(poolname,connection,datacenter_name)
end
it 'should return nil if the pool is not found' do
pending('https://github.com/puppetlabs/vmpooler/issues/209')
expect(subject.find_pool(missing_poolname,connection,datacenter_name)).to be_nil
end
end
context 'with multiple datacenters' do
let(:poolpath) { 'pool' }
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => 'AnotherDC',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => nil,
},
},
{ :name => datacenter_name,
:hostfolder_tree => {
'folder3' => nil,
'pool' => {:object_type => 'resource_pool'},
'folder4' => nil,
},
}
]
}
}}
it 'should return the pool when found' do
result = subject.find_pool(poolpath,connection, datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq('pool')
expect(result.is_a?(RbVmomi::VIM::ResourcePool)).to be true
end
end
[
# Single layer Host folder hierarchy
{
:context => 'single layer folder hierarchy with a resource pool',
:poolpath => 'pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => nil,
'pool' => {:object_type => 'resource_pool'},
'folder3' => nil,
},
},
{
:context => 'single layer folder hierarchy with a child resource pool',
:poolpath => 'parentpool/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => nil,
'parentpool' => {:object_type => 'resource_pool', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
'folder3' => nil,
},
},
{
:context => 'single layer folder hierarchy with a resource pool within a cluster',
:poolpath => 'cluster/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => nil,
'cluster' => {:object_type => 'cluster_compute_resource', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
'folder3' => nil,
},
},
# Multi layer Host folder hierarchy
{
:context => 'multi layer folder hierarchy with a resource pool',
:poolpath => 'folder2/folder4/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => { :children => {
'folder3' => nil,
'folder4' => { :children => {
'pool' => {:object_type => 'resource_pool'},
}},
}},
'folder5' => nil,
},
},
{
:context => 'multi layer folder hierarchy with a child resource pool',
:poolpath => 'folder2/folder4/parentpool/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => { :children => {
'folder3' => nil,
'folder4' => { :children => {
'parentpool' => {:object_type => 'resource_pool', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
}},
}},
'folder5' => nil,
},
},
{
:context => 'multi layer folder hierarchy with a resource pool within a cluster',
:poolpath => 'folder2/folder4/cluster/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => { :children => {
'folder3' => nil,
'folder4' => { :children => {
'cluster' => {:object_type => 'cluster_compute_resource', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
}},
}},
'folder5' => nil,
},
},
].each do |testcase|
context testcase[:context] do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:hostfolder_tree => testcase[:hostfolder_tree],
}
]
}
}}
it 'should return the pool when found' do
result = subject.find_pool(testcase[:poolpath],connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(testcase[:poolname])
expect(result.is_a?(RbVmomi::VIM::ResourcePool)).to be true
end
it 'should return nil if the poolname is not found' do
pending('https://github.com/puppetlabs/vmpooler/issues/209')
expect(subject.find_pool(missing_poolname,connection,datacenter_name)).to be_nil
end
end
end
# Tests for issue https://github.com/puppetlabs/vmpooler/issues/210
[
{
:context => 'multi layer folder hierarchy with a resource pool the same name as a folder',
:poolpath => 'folder2/folder4/cluster/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => { :children => {
'folder3' => nil,
'bad_pool' => {:object_type => 'resource_pool', :name => 'folder4'},
'folder4' => { :children => {
'cluster' => {:object_type => 'cluster_compute_resource', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
}},
}},
'folder5' => nil,
},
},
{
:context => 'multi layer folder hierarchy with a cluster the same name as a folder',
:poolpath => 'folder2/folder4/cluster/pool',
:poolname => 'pool',
:hostfolder_tree => {
'folder1' => nil,
'folder2' => { :children => {
'folder3' => nil,
'bad_cluster' => {:object_type => 'cluster_compute_resource', :name => 'folder4'},
'folder4' => { :children => {
'cluster' => {:object_type => 'cluster_compute_resource', :children => {
'pool' => {:object_type => 'resource_pool'},
}},
}},
}},
'folder5' => nil,
},
},
].each do |testcase|
context testcase[:context] do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:hostfolder_tree => testcase[:hostfolder_tree],
}
]
}
}}
it 'should ensure the connection' do
pending('https://github.com/puppetlabs/vmpooler/issues/210')
expect(subject).to receive(:ensure_connected)
subject.find_pool(testcase[:poolpath],connection,datacenter_name)
end
it 'should return the pool when found' do
pending('https://github.com/puppetlabs/vmpooler/issues/210')
result = subject.find_pool(testcase[:poolpath],connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(testcase[:poolname])
expect(result.is_a?(RbVmomi::VIM::ResourcePool)).to be true
end
end
end
end
describe '#find_snapshot' do
let(:snapshot_name) {'snapshot'}
let(:missing_snapshot_name) {'missing_snapshot'}
let(:vm) { mock_RbVmomi_VIM_VirtualMachine(mock_options) }
let(:snapshot_object) { mock_RbVmomi_VIM_VirtualMachineSnapshot() }
context 'VM with no snapshots' do
let(:mock_options) {{ :snapshot_tree => nil }}
it 'should return nil' do
expect(subject.find_snapshot(vm,snapshot_name)).to be_nil
end
end
context 'VM with a single layer of snapshots' do
let(:mock_options) {{
:snapshot_tree => {
'snapshot1' => nil,
'snapshot2' => nil,
'snapshot3' => nil,
'snapshot4' => nil,
snapshot_name => { :ref => snapshot_object},
}
}}
it 'should return snapshot which matches the name' do
result = subject.find_snapshot(vm,snapshot_name)
expect(result).to be(snapshot_object)
end
it 'should return nil which no matches are found' do
result = subject.find_snapshot(vm,missing_snapshot_name)
expect(result).to be_nil
end
end
context 'VM with a nested layers of snapshots' do
let(:mock_options) {{
:snapshot_tree => {
'snapshot1' => nil,
'snapshot2' => nil,
'snapshot3' => { :children => {
'snapshot4' => nil,
'snapshot5' => { :children => {
snapshot_name => { :ref => snapshot_object},
}},
}},
'snapshot6' => nil,
}
}}
it 'should return snapshot which matches the name' do
result = subject.find_snapshot(vm,snapshot_name)
expect(result).to be(snapshot_object)
end
it 'should return nil which no matches are found' do
result = subject.find_snapshot(vm,missing_snapshot_name)
expect(result).to be_nil
end
end
end
describe '#find_vm' do
before(:each) do
allow(subject).to receive(:find_vm_light).and_return('vmlight')
allow(subject).to receive(:find_vm_heavy).and_return( { vmname => 'vmheavy' })
end
it 'should call find_vm_light' do
expect(subject).to receive(:find_vm_light).and_return('vmlight')
expect(subject.find_vm(vmname,connection)).to eq('vmlight')
end
it 'should not call find_vm_heavy if find_vm_light finds the VM' do
expect(subject).to receive(:find_vm_light).and_return('vmlight')
expect(subject).to receive(:find_vm_heavy).exactly(0).times
expect(subject.find_vm(vmname,connection)).to eq('vmlight')
end
it 'should call find_vm_heavy when find_vm_light returns nil' do
expect(subject).to receive(:find_vm_light).and_return(nil)
expect(subject).to receive(:find_vm_heavy).and_return( { vmname => 'vmheavy' })
expect(subject.find_vm(vmname,connection)).to eq('vmheavy')
end
end
describe '#find_vm_light' do
let(:missing_vm) { 'missing_vm' }
before(:each) do
allow(connection.searchIndex).to receive(:FindByDnsName).and_return(nil)
end
it 'should call FindByDnsName with the correct parameters' do
expect(connection.searchIndex).to receive(:FindByDnsName).with({
:vmSearch => true,
dnsName: vmname,
})
subject.find_vm_light(vmname,connection)
end
it 'should return the VM object when found' do
vm_object = mock_RbVmomi_VIM_VirtualMachine()
expect(connection.searchIndex).to receive(:FindByDnsName).with({
:vmSearch => true,
dnsName: vmname,
}).and_return(vm_object)
expect(subject.find_vm_light(vmname,connection)).to be(vm_object)
end
it 'should return nil if the VM is not found' do
expect(connection.searchIndex).to receive(:FindByDnsName).with({
:vmSearch => true,
dnsName: missing_vm,
}).and_return(nil)
expect(subject.find_vm_light(missing_vm,connection)).to be_nil
end
end
describe '#find_vm_heavy' do
let(:missing_vm) { 'missing_vm' }
# Return an empty result by default
let(:retrieve_result) {{}}
before(:each) do
allow(connection.propertyCollector).to receive(:RetrievePropertiesEx).and_return(mock_RbVmomi_VIM_RetrieveResult(retrieve_result))
end
context 'Search result is empty' do
it 'should return empty hash' do
expect(subject.find_vm_heavy(vmname,connection)).to eq({})
end
end
context 'Search result contains VMs but no matches' do
let(:retrieve_result) {
{ :response => [
{ 'name' => 'no_match001'},
{ 'name' => 'no_match002'},
{ 'name' => 'no_match003'},
{ 'name' => 'no_match004'},
]
}
}
it 'should return empty hash' do
expect(subject.find_vm_heavy(vmname,connection)).to eq({})
end
end
context 'Search contains a single match' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname })}
let(:retrieve_result) {
{ :response => [
{ 'name' => 'no_match001'},
{ 'name' => 'no_match002'},
{ 'name' => vmname, :object => vm_object },
{ 'name' => 'no_match003'},
{ 'name' => 'no_match004'},
]
}
}
it 'should return single result' do
result = subject.find_vm_heavy(vmname,connection)
expect(result.keys.count).to eq(1)
end
it 'should return the matching VM Object' do
result = subject.find_vm_heavy(vmname,connection)
expect(result[vmname]).to be(vm_object)
end
end
context 'Search contains a two matches' do
let(:vm_object1) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname })}
let(:vm_object2) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname })}
let(:retrieve_result) {
{ :response => [
{ 'name' => 'no_match001'},
{ 'name' => 'no_match002'},
{ 'name' => vmname, :object => vm_object1 },
{ 'name' => 'no_match003'},
{ 'name' => 'no_match004'},
{ 'name' => vmname, :object => vm_object2 },
]
}
}
it 'should return one result' do
result = subject.find_vm_heavy(vmname,connection)
expect(result.keys.count).to eq(1)
end
it 'should return the last matching VM Object' do
result = subject.find_vm_heavy(vmname,connection)
expect(result[vmname]).to be(vm_object2)
end
end
end
describe '#find_vmdks' do
let(:datastorename) { 'datastore' }
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name, :datastores => [datastorename] }
]
}
}}
let(:collectMultiple_response) { {} }
before(:each) do
allow(connection.serviceContent.propertyCollector).to receive(:collectMultiple).and_return(collectMultiple_response)
end
context 'Searching all files for all VMs on a Datastore' do
# This is fairly fragile mocking
let(:collectMultiple_response) { {
'FakeVMObject1' => { 'layoutEx.file' =>
[
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 101, :name => "[#{datastorename}] mock1/mock1_0.vmdk"})
]},
vmname => { 'layoutEx.file' =>
[
# VMDKs which should match
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 1, :name => "[#{datastorename}] #{vmname}/#{vmname}_0.vmdk"}),
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 2, :name => "[#{datastorename}] #{vmname}/#{vmname}_1.vmdk"}),
# VMDKs which should not match
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 102, :name => "[otherdatastore] #{vmname}/#{vmname}_0.vmdk"}),
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 103, :name => "[otherdatastore] #{vmname}/#{vmname}.vmdk"}),
mock_RbVmomi_VIM_VirtualMachineFileLayoutExFileInfo({ :key => 104, :name => "[otherdatastore] #{vmname}/#{vmname}_abc.vmdk"}),
]},
} }
it 'should return empty array if no VMDKs match the VM name' do
expect(subject.find_vmdks('missing_vm_name',datastorename,connection,datacenter_name)).to eq([])
end
it 'should return matching VMDKs for the VM' do
result = subject.find_vmdks(vmname,datastorename,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.count).to eq(2)
# The keys for each VMDK should be less that 100 as per the mocks
result.each do |fileinfo|
expect(fileinfo.key).to be < 100
end
end
end
end
describe '#get_base_vm_container_from' do
it 'should return a recursive view of type VirtualMachine' do
result = subject.get_base_vm_container_from(connection)
expect(result.recursive).to be true
expect(result.type).to eq(['VirtualMachine'])
end
end
describe '#get_snapshot_list' do
let(:snapshot_name) {'snapshot'}
let(:snapshot_tree) { mock_RbVmomi_VIM_VirtualMachine(mock_options).snapshot.rootSnapshotList }
let(:snapshot_object) { mock_RbVmomi_VIM_VirtualMachine() }
it 'should raise if the snapshot tree is nil' do
expect{ subject.get_snapshot_list(nil,snapshot_name)}.to raise_error(NoMethodError)
end
context 'VM with a single layer of snapshots' do
let(:mock_options) {{
:snapshot_tree => {
'snapshot1' => nil,
'snapshot2' => nil,
'snapshot3' => nil,
'snapshot4' => nil,
snapshot_name => { :ref => snapshot_object},
}
}}
it 'should return snapshot which matches the name' do
result = subject.get_snapshot_list(snapshot_tree,snapshot_name)
expect(result).to be(snapshot_object)
end
end
context 'VM with a nested layers of snapshots' do
let(:mock_options) {{
:snapshot_tree => {
'snapshot1' => nil,
'snapshot2' => nil,
'snapshot3' => { :children => {
'snapshot4' => nil,
'snapshot5' => { :children => {
snapshot_name => { :ref => snapshot_object},
}},
}},
'snapshot6' => nil,
}
}}
it 'should return snapshot which matches the name' do
result = subject.get_snapshot_list(snapshot_tree,snapshot_name)
expect(result).to be(snapshot_object)
end
end
end
describe '#migrate_vm_host' do
let(:vm_object) { mock_RbVmomi_VIM_VirtualMachine({ :name => vmname })}
let(:host_object) { mock_RbVmomi_VIM_HostSystem({ :name => 'HOST' })}
let(:relocate_task) { mock_RbVmomi_VIM_Task() }
before(:each) do
allow(vm_object).to receive(:RelocateVM_Task).and_return(relocate_task)
allow(relocate_task).to receive(:wait_for_completion)
end
it 'should call RelovateVM_Task' do
expect(vm_object).to receive(:RelocateVM_Task).and_return(relocate_task)
subject.migrate_vm_host(vm_object,host_object)
end
it 'should use a Relocation Spec object with correct host' do
expect(vm_object).to receive(:RelocateVM_Task).with(relocation_spec_with_host(host_object))
subject.migrate_vm_host(vm_object,host_object)
end
it 'should wait for the relocation to complete' do
expect(relocate_task).to receive(:wait_for_completion)
subject.migrate_vm_host(vm_object,host_object)
end
it 'should return the result of the relocation' do
expect(relocate_task).to receive(:wait_for_completion).and_return('RELOCATE_RESULT')
expect(subject.migrate_vm_host(vm_object,host_object)).to eq('RELOCATE_RESULT')
end
end
end