diff --git a/lib/vmpooler/providers/vsphere.rb b/lib/vmpooler/providers/vsphere.rb index c77f651..88be86d 100644 --- a/lib/vmpooler/providers/vsphere.rb +++ b/lib/vmpooler/providers/vsphere.rb @@ -45,7 +45,7 @@ module Vmpooler @connection_pool.with_metrics do |pool_object| connection = ensured_vsphere_connection(pool_object) foldername = pool_config(pool_name)['folder'] - folder_object = find_folder(foldername, connection) + folder_object = find_folder(foldername, connection, get_target_datacenter_from_config(pool_name)) return vms if folder_object.nil? @@ -94,7 +94,7 @@ module Vmpooler raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil? target_cluster_name = get_target_cluster_from_config(pool_name) - cluster = find_cluster(target_cluster_name, connection) + cluster = find_cluster(target_cluster_name, connection, get_target_datacenter_from_config(pool_name)) raise("Pool #{pool_name} specifies cluster #{target_cluster_name} which does not exist for the provider #{name}") if cluster.nil? # Go through each host and initiate a migration when the correct host name is found @@ -142,6 +142,7 @@ module Vmpooler target_folder_path = pool['folder'] target_datastore = pool['datastore'] target_cluster_name = get_target_cluster_from_config(pool_name) + target_datacenter_name = get_target_datacenter_from_config(pool_name) # Extract the template VM name from the full path raise("Pool #{pool_name} did specify a full path for the template for the provider #{name}") unless template_path =~ /\// @@ -149,7 +150,7 @@ module Vmpooler template_name = templatefolders.pop # Get the actual objects from vSphere - template_folder_object = find_folder(templatefolders.join('/'), connection) + template_folder_object = find_folder(templatefolders.join('/'), connection, target_datacenter_name) raise("Pool #{pool_name} specifies a template folder of #{templatefolders.join('/')} which does not exist for the provider #{name}") if template_folder_object.nil? template_vm_object = template_folder_object.find(template_name) @@ -170,11 +171,11 @@ module Vmpooler ) # Choose a cluster/host to place the new VM on - target_host_object = find_least_used_host(target_cluster_name, connection) + target_host_object = find_least_used_host(target_cluster_name, connection, target_datacenter_name) # Put the VM in the specified folder and resource pool relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec( - datastore: find_datastore(target_datastore, connection), + datastore: find_datastore(target_datastore, connection, target_datacenter_name), host: target_host_object, diskMoveType: :moveChildMostDiskBacking ) @@ -189,7 +190,7 @@ module Vmpooler # Create the new VM new_vm_object = template_vm_object.CloneVM_Task( - folder: find_folder(target_folder_path, connection), + folder: find_folder(target_folder_path, connection, target_datacenter_name), name: new_vmname, spec: clone_spec ).wait_for_completion @@ -211,7 +212,7 @@ module Vmpooler vm_object = find_vm(vm_name, connection) raise("VM #{vm_name} in pool #{pool_name} does not exist for the provider #{name}") if vm_object.nil? - add_disk(vm_object, disk_size, datastore_name, connection) + add_disk(vm_object, disk_size, datastore_name, connection, get_target_datacenter_from_config(pool_name)) end true end @@ -287,6 +288,16 @@ module Vmpooler nil end + def get_target_datacenter_from_config(pool_name) + pool = pool_config(pool_name) + return nil if pool.nil? + + return pool['datacenter'] unless pool['datacenter'].nil? + return provider_config['datacenter'] unless provider_config['datacenter'].nil? + + nil + end + def generate_vm_hash(vm_object, template_name, pool_name) hash = { 'name' => nil, 'hostname' => nil, 'template' => nil, 'poolname' => nil, 'boottime' => nil, 'powerstate' => nil } @@ -375,11 +386,12 @@ module Vmpooler (full_path.reverse.map { |p| p[1] }).join('/') end - def add_disk(vm, size, datastore, connection) + def add_disk(vm, size, datastore, connection, datacentername) return false unless size.to_i > 0 - vmdk_datastore = find_datastore(datastore, connection) - vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{find_vmdks(vm['name'], datastore, connection).length + 1}.vmdk" + vmdk_datastore = find_datastore(datastore, connection, datacentername) + raise("Datastore '#{datastore}' does not exist in datacenter '#{datacentername}'") if vmdk_datastore.nil? + vmdk_file_name = "#{vm['name']}/#{vm['name']}_#{find_vmdks(vm['name'], datastore, connection, datacentername).length + 1}.vmdk" controller = find_disk_controller(vm) @@ -413,7 +425,7 @@ module Vmpooler ) connection.serviceContent.virtualDiskManager.CreateVirtualDisk_Task( - datacenter: connection.serviceInstance.find_datacenter, + datacenter: connection.serviceInstance.find_datacenter(datacentername), name: "[#{vmdk_datastore.name}] #{vmdk_file_name}", spec: vmdk_spec ).wait_for_completion @@ -423,8 +435,9 @@ module Vmpooler true end - def find_datastore(datastorename, connection) - datacenter = connection.serviceInstance.find_datacenter + def find_datastore(datastorename, connection, datacentername) + datacenter = connection.serviceInstance.find_datacenter(datacentername) + raise("Datacenter #{datacentername} does not exist") if datacenter.nil? datacenter.find_datastore(datastorename) end @@ -497,8 +510,9 @@ module Vmpooler available_unit_numbers.sort[0] end - def find_folder(foldername, connection) - datacenter = connection.serviceInstance.find_datacenter + def find_folder(foldername, connection, datacentername) + datacenter = connection.serviceInstance.find_datacenter(datacentername) + raise("Datacenter #{datacentername} does not exist") if datacenter.nil? base = datacenter.vmFolder folders = foldername.split('/') @@ -558,16 +572,17 @@ module Vmpooler (memory_usage.to_f / memory_size.to_f) * 100 end - def find_least_used_host(cluster, connection) - cluster_object = find_cluster(cluster, connection) + def find_least_used_host(cluster, connection, datacentername) + cluster_object = find_cluster(cluster, connection, datacentername) target_hosts = get_cluster_host_utilization(cluster_object) raise("There is no host candidate in vcenter that meets all the required conditions, check that the cluster has available hosts in a 'green' status, not in maintenance mode and not overloaded CPU and memory'") if target_hosts.empty? least_used_host = target_hosts.sort[0][1] least_used_host end - def find_cluster(cluster, connection) - datacenter = connection.serviceInstance.find_datacenter + def find_cluster(cluster, connection, datacentername) + datacenter = connection.serviceInstance.find_datacenter(datacentername) + raise("Datacenter #{datacentername} does not exist") if datacenter.nil? datacenter.hostFolder.children.find { |cluster_object| cluster_object.name == cluster } end @@ -590,8 +605,9 @@ module Vmpooler [target_host, target_host.name] end - def find_pool(poolname, connection) - datacenter = connection.serviceInstance.find_datacenter + def find_pool(poolname, connection, datacentername) + datacenter = connection.serviceInstance.find_datacenter(datacentername) + raise("Datacenter #{datacentername} does not exist") if datacenter.nil? base = datacenter.hostFolder pools = poolname.split('/') pools.each do |pool| @@ -670,10 +686,10 @@ module Vmpooler vms end - def find_vmdks(vmname, datastore, connection) + def find_vmdks(vmname, datastore, connection, datacentername) disks = [] - vmdk_datastore = find_datastore(datastore, connection) + vmdk_datastore = find_datastore(datastore, connection,datacentername) vm_files = connection.serviceContent.propertyCollector.collectMultiple vmdk_datastore.vm, 'layoutEx.file' vm_files.keys.each do |f| diff --git a/spec/rbvmomi_helper.rb b/spec/rbvmomi_helper.rb index 375fd56..dec86d1 100644 --- a/spec/rbvmomi_helper.rb +++ b/spec/rbvmomi_helper.rb @@ -123,6 +123,7 @@ MockServiceInstance = Struct.new( return child if path.nil? || child.name == path end end + nil end end diff --git a/spec/unit/providers/vsphere_spec.rb b/spec/unit/providers/vsphere_spec.rb index 224166b..68af47b 100644 --- a/spec/unit/providers/vsphere_spec.rb +++ b/spec/unit/providers/vsphere_spec.rb @@ -42,6 +42,7 @@ describe 'Vmpooler::PoolManager::Provider::VSphere' do let(:metrics) { Vmpooler::DummyStatsd.new } let(:poolname) { 'pool1'} let(:provider_options) { { 'param' => 'value' } } + let(:datacenter_name) { 'MockDC' } let(:config) { YAML.load(<<-EOT --- :config: @@ -55,6 +56,7 @@ describe 'Vmpooler::PoolManager::Provider::VSphere' do 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' ] @@ -95,7 +97,7 @@ EOT 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) + expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(nil) end it 'should get a connection' do @@ -113,7 +115,7 @@ EOT 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) + expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object) end it 'should get a connection' do @@ -142,7 +144,7 @@ EOT folder_object.childEntity << mock_vm end - expect(subject).to receive(:find_folder).with(pool_config['folder'],connection).and_return(folder_object) + expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object) end it 'should get a connection' do @@ -324,7 +326,7 @@ EOT before(:each) do config[:pools][0]['clone_target'] = cluster_name - expect(subject).to receive(:find_cluster).with(cluster_name,connection).and_return(nil) + expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(nil) end it 'should raise an error' do @@ -338,7 +340,7 @@ EOT 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) + expect(subject).to receive(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(nil) end it 'should raise an error' do @@ -352,7 +354,7 @@ EOT 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(:find_cluster).with(cluster_name,connection,datacenter_name).and_return(mock_cluster) expect(subject).to receive(:migrate_vm_host).exactly(0).times end @@ -367,7 +369,7 @@ EOT 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(: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 @@ -382,7 +384,7 @@ EOT 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(: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 @@ -558,7 +560,7 @@ EOT context 'Given a successful creation' do before(:each) do - template_vm = subject.find_folder('Templates',connection).find('pool1') + 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 @@ -570,7 +572,7 @@ EOT end it 'should use the appropriate Create_VM spec' do - template_vm = subject.find_folder('Templates',connection).find('pool1') + 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) @@ -633,7 +635,7 @@ EOT context 'when adding the disk succeeds' do before(:each) do - expect(subject).to receive(:add_disk).with(vm_object, disk_size, datastorename, connection) + expect(subject).to receive(:add_disk).with(vm_object, disk_size, datastorename, connection, datacenter_name) end it 'should return true' do @@ -881,6 +883,92 @@ EOT 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 @@ -1227,7 +1315,7 @@ EOT let(:connection_options) {{ :serviceContent => { :datacenters => [ - { :name => 'MockDC', :datastores => [datastorename] } + { :name => datacenter_name, :datastores => [datastorename] } ] } }} @@ -1240,7 +1328,11 @@ EOT 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(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 @@ -1250,7 +1342,7 @@ EOT context 'Succesfully addding disk' do it 'should return true' do - expect(subject.add_disk(vm_object,disk_size,datastorename,connection)).to be true + 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 @@ -1259,13 +1351,13 @@ EOT .and_return(create_virtual_disk_task) - subject.add_disk(vm_object,disk_size,datastorename,connection) + 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)).to be false + expect(subject.add_disk(vm_object,0,datastorename,connection,datacenter_name)).to be false end end @@ -1273,13 +1365,28 @@ EOT let(:connection_options) {{ :serviceContent => { :datacenters => [ - { :name => 'MockDC', :datastores => ['missing_datastore'] } + { :name => datacenter_name, :datastores => ['missing_datastore'] } ] } }} - it 'should return false' do - expect{ subject.add_disk(vm_object,disk_size,datastorename,connection) }.to raise_error(NoMethodError) + 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 @@ -1293,7 +1400,7 @@ EOT } it 'should raise an error' do - expect{ subject.add_disk(vm_object,disk_size,datastorename,connection) }.to raise_error(NoMethodError) + expect{ subject.add_disk(vm_object,disk_size,datastorename,connection,datacenter_name) }.to raise_error(NoMethodError) end end end @@ -1306,13 +1413,13 @@ EOT let(:connection_options) {{ :serviceContent => { :datacenters => [ - { :name => 'MockDC', :datastores => [] } + { :name => datacenter_name, :datastores => [] } ] } }} it 'should return nil if the datastore is not found' do - result = subject.find_datastore(datastorename,connection) + result = subject.find_datastore(datastorename,connection,datacenter_name) expect(result).to be_nil end end @@ -1321,18 +1428,42 @@ EOT let(:connection_options) {{ :serviceContent => { :datacenters => [ - { :name => 'MockDC', :datastores => ['ds1','ds2',datastorename,'ds3'] } + { :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) + 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) + 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 @@ -1558,54 +1689,102 @@ EOT 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() } + 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)).to be_nil + expect(subject.find_folder(missing_foldername,connection,datacenter_name)).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, + 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) + 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)).to be_nil + 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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ - :vmfolder_tree => { - 'folder1' => nil, - 'vm1' => { :object_type => 'vm', :name => foldername }, - foldername => nil, - 'folder3' => nil, + 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) + 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 @@ -1615,31 +1794,37 @@ EOT 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, + let(:connection_options) {{ + :serviceContent => { + :datacenters => [ + { :name => datacenter_name, + :vmfolder_tree => { + 'folder1' => nil, + 'folder2' => { + :children => { + 'folder3' => nil, + 'folder4' => { + :children => { + end_folder_name => nil, + }, + } + }, }, + 'folder5' => nil, } - }, - }, - 'folder5' => nil, + } + ] } - }) } + }} it 'should return the folder when found' do - result = subject.find_folder(foldername,connection) + 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)).to be_nil + expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil end end @@ -1648,30 +1833,36 @@ EOT # 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, + 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, } - }, - }, - 'folder5' => nil, + } + ] } - }) } + }} it 'should not return a VM' do pending('https://github.com/puppetlabs/vmpooler/issues/204') - result = subject.find_folder(foldername,connection) + 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 + expect(result.is_a? RbVmomi::VIM::VirtualMachine,datacenter_name).to be false end end end @@ -1920,9 +2111,9 @@ EOT :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)}.to raise_error(NoMethodError,/undefined method/) + expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/) end end @@ -1935,7 +2126,7 @@ EOT let(:expected_host) { cluster_object.host[0] } it 'should return the standalone host' do - result = subject.find_least_used_host(cluster_name,connection) + result = subject.find_least_used_host(cluster_name,connection,datacenter_name) expect(result).to be(expected_host) end @@ -1951,7 +2142,7 @@ EOT 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/) + expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/) end end @@ -1966,7 +2157,7 @@ EOT let(:expected_host) { cluster_object.host[1] } it 'should return the standalone host' do - result = subject.find_least_used_host(cluster_name,connection) + result = subject.find_least_used_host(cluster_name,connection,datacenter_name) expect(result).to be(expected_host) end @@ -1983,7 +2174,7 @@ EOT 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/) + expect{subject.find_least_used_host(missing_cluster_name,connection,datacenter_name)}.to raise_error(NoMethodError,/undefined method/) end end @@ -2000,7 +2191,7 @@ EOT let(:expected_host) { cluster_object.host[1] } it 'should return the standalone host' do - result = subject.find_least_used_host(cluster_name,connection) + result = subject.find_least_used_host(cluster_name,connection,datacenter_name) expect(result).to be(expected_host) end @@ -2018,7 +2209,7 @@ EOT it 'should return a host' do pending('https://github.com/puppetlabs/vmpooler/issues/206') - result = subject.find_least_used_host(missing_cluster_name,connection) + result = subject.find_least_used_host(missing_cluster_name,connection,datacenter_name) expect(result).to_not be_nil end end @@ -2028,66 +2219,109 @@ EOT 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 + 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)).to be_nil + expect(subject.find_cluster(missing_cluster,connection,datacenter_name)).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'}, + 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) + 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)).to be_nil + 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(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ - :hostfolder_tree => { - 'cluster1' => {:object_type => 'compute_resource'}, - 'folder2' => { - :children => { - cluster => {:object_type => 'compute_resource'}, + 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'}, + } } - }, - '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) + 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)).to be_nil + expect(subject.find_cluster(missing_cluster,connection,datacenter_name)).to be_nil end end end @@ -2287,23 +2521,56 @@ EOT 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() } + 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) + 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)).to be_nil + 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 @@ -2398,10 +2665,18 @@ EOT }, ].each do |testcase| context testcase[:context] do - let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :hostfolder_tree => testcase[:hostfolder_tree]}) } + 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) + result = subject.find_pool(testcase[:poolpath],connection,datacenter_name) expect(result).to_not be_nil expect(result.name).to eq(testcase[:poolname]) @@ -2410,7 +2685,7 @@ EOT 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 + expect(subject.find_pool(missing_poolname,connection,datacenter_name)).to be_nil end end end @@ -2455,18 +2730,26 @@ EOT }, ].each do |testcase| context testcase[:context] do - let(:datacenter_object) { mock_RbVmomi_VIM_Datacenter({ :hostfolder_tree => testcase[:hostfolder_tree]}) } + 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]) + 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]) + result = subject.find_pool(testcase[:poolpath],connection,datacenter_name) expect(result).to_not be_nil expect(result.name).to eq(testcase[:poolname]) @@ -2688,7 +2971,7 @@ EOT let(:connection_options) {{ :serviceContent => { :datacenters => [ - { :name => 'MockDC', :datastores => [datastorename] } + { :name => datacenter_name, :datastores => [datastorename] } ] } }} @@ -2719,11 +3002,11 @@ EOT } } it 'should return empty array if no VMDKs match the VM name' do - expect(subject.find_vmdks('missing_vm_name',datastorename,connection)).to eq([]) + 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) + 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 diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example index 6b0d4c6..e6686e1 100644 --- a/vmpooler.yaml.example +++ b/vmpooler.yaml.example @@ -6,6 +6,27 @@ # The currently supported backing services are: # - vsphere # - dummy +# +# - provider_class +# For multiple providers, specify one of the supported backing services (vsphere or dummy) +# (optional: will default to it's parent :key: name eg. 'vsphere') +# +# If you want to support more than one provider with different parameters (server, username or passwords) you have to specify the +# backing service in the provider_class configuration parameter for example 'vsphere' or 'dummy'. Each pool can specify +# the provider to use. +# +# Multiple providers example: + + :vsphere-pdx: + server: 'vsphere.pdx.company.com' + username: 'vmpooler-pdx' + password: 'swimsw1msw!m' + provider_class: 'vsphere' + :vsphere-bfs: + server: 'vsphere.bfs.company.com' + username: 'vmpooler-bfs' + password: 'swimsw1msw!m' + provider_class: 'vsphere' # :vsphere: # @@ -33,9 +54,9 @@ # Whether to ignore any HTTPS negotiation errors (e.g. untrusted self-signed certificates) # (optional: default true) # -# - provider_class -# For multiple providers, specify one of the supported backing services (vsphere or dummy) -# (optional: will default to it's parent :key: name eg. 'vsphere') +# - datacenter +# The datacenter within vCenter to manage VMs. This can be overridden in the pool configuration +# (optional: default is the first datacenter in vSphere) # # Example: @@ -44,23 +65,6 @@ username: 'vmpooler' password: 'swimsw1msw!m' -# If you want to support more than one provider with different parameters (server, username or passwords) you have to specify the -# backing service in the provider_class configuration parameter for example 'vsphere' or 'dummy'. Each pool can specify -# the provider to use. -# -# Multiple providers example: - - :vsphere-pdx: - server: 'vsphere.pdx.company.com' - username: 'vmpooler-pdx' - password: 'swimsw1msw!m' - provider_class: 'vsphere' - :vsphere-bfs: - server: 'vsphere.bfs.company.com' - username: 'vmpooler-bfs' - password: 'swimsw1msw!m' - provider_class: 'vsphere' - # :dummy: # # The dummy backing service is a simple text file service that can be used @@ -465,6 +469,10 @@ # The vSphere 'datastore' destination for spawned clones. # (required) # +# - datacenter +# The datacenter within vCenter to manage VMs. +# (optional: default is the first datacenter in vSphere) +# # Example: :pools: