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(: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 :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) } 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(:get_connection).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).and_return(nil) end it 'should get a connection' do expect(subject).to receive(:get_connection).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).and_return(folder_object) end it 'should get a connection' do expect(subject).to receive(:get_connection).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).and_return(folder_object) end it 'should get a connection' do expect(subject).to receive(:get_connection).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(:get_connection).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(:get_connection).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(:get_connection).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(:get_connection).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(:get_connection).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(:get_connection).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(:get_connection).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 # [ , ] 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(:get_connection).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(:get_connection).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).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).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).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).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).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(:get_connection).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(:get_connection).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).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).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(:get_connection).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) 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(:get_connection).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(:get_connection).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(:get_connection).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_connection' do before(:each) do # NOTE - Using instance_variable_set is a code smell of code that is not testable subject.instance_variable_set("@connection",connection) end context 'when connection is ok' do it 'should not attempt to reconnect' do expect(subject).to receive(:connect_to_vsphere).exactly(0).times subject.get_connection() end it 'should return a connection' do result = subject.get_connection() expect(result).to be(connection) end end context 'when connection has broken' do before(:each) do expect(connection.serviceInstance).to receive(:CurrentTime).and_raise(RuntimeError,'MockConnectionError') end it 'should not increment the connect.open metric' do # https://github.com/puppetlabs/vmpooler/issues/195 expect(metrics).to receive(:increment).with('connect.open').exactly(0).times allow(subject).to receive(:connect_to_vsphere) subject.get_connection() end it 'should call connect_to_vsphere to reconnect' do allow(metrics).to receive(:increment) expect(subject).to receive(:connect_to_vsphere).with(no_args) subject.get_connection() end it 'should return a new connection' do new_connection = mock_RbVmomi_VIM_Connection(connection_options) expect(subject).to receive(:connect_to_vsphere).with(no_args).and_return(new_connection) result = subject.get_connection() expect(result).to be(new_connection) 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 => 'MockDC', :datastores => [datastorename] } ] } }} let(:create_virtual_disk_task) { mock_RbVmomi_VIM_Task() } let(:reconfig_vm_task) { mock_RbVmomi_VIM_Task() } before(:each) do # NOTE - This method should not be using `_connection`, instead it should be using `@conection` # This should not be required once https://github.com/puppetlabs/vmpooler/issues/213 is resolved mock_ds = subject.find_datastore(datastorename,connection) allow(mock_ds).to receive(:_connection).and_return(connection) unless mock_ds.nil? # 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).and_return(create_virtual_disk_task) 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)).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) end end context 'Requested disk size is 0' do it 'should raise an error' do expect(subject.add_disk(vm_object,0,datastorename,connection)).to be false end end context 'No datastores or datastore missing' do let(:connection_options) {{ :serviceContent => { :datacenters => [ { :name => 'MockDC', :datastores => ['missing_datastore'] } ] } }} it 'should return false' do expect{ subject.add_disk(vm_object,disk_size,datastorename,connection) }.to raise_error(NoMethodError) 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) }.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 => 'MockDC', :datastores => [] } ] } }} it 'should return nil if the datastore is not found' do result = subject.find_datastore(datastorename,connection) expect(result).to be_nil end end context 'Many datastores in the datacenter' do let(:connection_options) {{ :serviceContent => { :datacenters => [ { :name => 'MockDC', :datastores => ['ds1','ds2',datastorename,'ds3'] } ] } }} it 'should return nil if the datastore is not found' do result = subject.find_datastore('missing_datastore',connection) expect(result).to be_nil end it 'should find the datastore in the datacenter' do result = subject.find_datastore(datastorename,connection) 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'} before(:each) do allow(connection.serviceInstance).to receive(:find_datacenter).and_return(datacenter_object) end context 'with no folder hierarchy' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter() } it 'should return nil if the folder is not found' do expect(subject.find_folder(missing_foldername,connection)).to be_nil end end context 'with a single layer folder hierarchy' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :vmfolder_tree => { 'folder1' => nil, 'folder2' => nil, foldername => nil, 'folder3' => nil, } }) } it 'should return the folder when found' do result = subject.find_folder(foldername,connection) 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)).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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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) 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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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) 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)).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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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) expect(result).to_not be_nil expect(result.name).to eq(foldername) expect(result.is_a? RbVmomi::VIM::VirtualMachine).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] } it 'should raise an error' do expect{subject.find_least_used_host(missing_cluster_name,connection)}.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) 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)}.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) 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)}.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) 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) expect(result).to_not be_nil end end end describe '#find_cluster' do let(:cluster) {'cluster'} let(:missing_cluster) {'missing_cluster'} before(:each) do allow(connection.serviceInstance).to receive(:find_datacenter).and_return(datacenter_object) end context 'no clusters in the datacenter' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter() } before(:each) do end it 'should return nil if the cluster is not found' do expect(subject.find_cluster(missing_cluster,connection)).to be_nil end end context 'with a single layer folder hierarchy' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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) 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)).to be_nil end end context 'with a multi layer folder hierarchy' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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) 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)).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(NoMethodError,/undefined method/) 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(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({: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'} before(:each) do allow(connection.serviceInstance).to receive(:find_datacenter).and_return(datacenter_object) end context 'with empty folder hierarchy' do let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter() } 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) 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)).to be_nil 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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :hostfolder_tree => testcase[:hostfolder_tree]}) } it 'should return the pool when found' do result = subject.find_pool(testcase[:poolpath],connection) 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)).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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :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]) end it 'should return the pool when found' do pending('https://github.com/puppetlabs/vmpooler/issues/210') result = subject.find_pool(testcase[:poolpath]) 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 => 'MockDC', :datastores => [datastorename] } ] } }} let(:collectMultiple_response) { {} } before(:each) do # NOTE - This method should not be using `_connection`, instead it should be using `@conection` mock_ds = subject.find_datastore(datastorename,connection) allow(mock_ds).to receive(:_connection).and_return(connection) allow(connection.serviceContent.propertyCollector).to receive(:collectMultiple).and_return(collectMultiple_response) end it 'should not use _connction to get the underlying connection object' do pending('https://github.com/puppetlabs/vmpooler/issues/213') mock_ds = subject.find_datastore(datastorename) expect(mock_ds).to receive(:_connection).exactly(0).times begin # ignore all errors. What's important is that it doesn't call _connection subject.find_vmdks(vmname,datastorename,connection) rescue end 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)).to eq([]) end it 'should return matching VMDKs for the VM' do result = subject.find_vmdks(vmname,datastorename,connection) 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 describe '#close' do context 'no connection has been made' do before(:each) do # NOTE - Using instance_variable_set is a code smell of code that is not testable subject.instance_variable_set("@connection",nil) end it 'should not error' do pending('https://github.com/puppetlabs/vmpooler/issues/211') subject.close end end context 'on an open connection' do before(:each) do # NOTE - Using instance_variable_set is a code smell of code that is not testable subject.instance_variable_set("@connection",connection) end it 'should close the underlying connection object' do expect(connection).to receive(:close) subject.close end end end end