Merge pull request #268 from mattkirby/reduce_object_lookups

Reduce object lookups for finding folders
This commit is contained in:
mchllweeks 2018-07-09 12:46:17 -07:00 committed by GitHub
commit a72012b754
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 176 deletions

View file

@ -46,8 +46,7 @@ module Vmpooler
vms = [] vms = []
@connection_pool.with_metrics do |pool_object| @connection_pool.with_metrics do |pool_object|
connection = ensured_vsphere_connection(pool_object) connection = ensured_vsphere_connection(pool_object)
foldername = pool_config(pool_name)['folder'] folder_object = find_vm_folder(pool_name, connection)
folder_object = find_folder(foldername, connection, get_target_datacenter_from_config(pool_name))
return vms if folder_object.nil? return vms if folder_object.nil?
@ -168,18 +167,7 @@ module Vmpooler
vm_object = find_vm(pool_name, vm_name, connection) vm_object = find_vm(pool_name, vm_name, connection)
return vm_hash if vm_object.nil? return vm_hash if vm_object.nil?
vm_folder_path = get_vm_folder_path(vm_object) vm_hash = generate_vm_hash(vm_object, pool_name)
# Find the pool name based on the folder path
pool_name = nil
template_name = nil
global_config[:pools].each do |pool|
if pool['folder'] == vm_folder_path
pool_name = pool['name']
template_name = pool['template']
end
end
vm_hash = generate_vm_hash(vm_object, template_name, pool_name)
end end
vm_hash vm_hash
end end
@ -243,7 +231,7 @@ module Vmpooler
) )
begin begin
vm_target_folder = find_folder(target_folder_path, connection, target_datacenter_name) vm_target_folder = find_vm_folder(pool_name, connection)
if vm_target_folder.nil? and @config[:config].key?('create_folders') and @config[:config]['create_folders'] == true if vm_target_folder.nil? and @config[:config].key?('create_folders') and @config[:config]['create_folders'] == true
vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name) vm_target_folder = create_folder(connection, target_folder_path, target_datacenter_name)
end end
@ -262,7 +250,7 @@ module Vmpooler
spec: clone_spec spec: clone_spec
).wait_for_completion ).wait_for_completion
vm_hash = generate_vm_hash(new_vm_object, template_path, pool_name) vm_hash = generate_vm_hash(new_vm_object, pool_name)
end end
vm_hash vm_hash
end end
@ -365,15 +353,24 @@ module Vmpooler
nil nil
end end
def generate_vm_hash(vm_object, template_name, pool_name) # Return a hash of VM data
hash = { 'name' => nil, 'hostname' => nil, 'template' => nil, 'poolname' => nil, 'boottime' => nil, 'powerstate' => nil } # Provides vmname, hostname, template, poolname, boottime and powerstate information
def generate_vm_hash(vm_object, pool_name)
pool_configuration = pool_config(pool_name)
return nil if pool_configuration.nil?
hash['name'] = vm_object.name hostname = vm_object.summary.guest.hostName if vm_object.summary && vm_object.summary.guest && vm_object.summary.guest.hostName
hash['hostname'] = vm_object.summary.guest.hostName if vm_object.summary && vm_object.summary.guest && vm_object.summary.guest.hostName boottime = vm_object.runtime.bootTime if vm_object.runtime && vm_object.runtime.bootTime
hash['template'] = template_name powerstate = vm_object.runtime.powerState if vm_object.runtime && vm_object.runtime.powerState
hash['poolname'] = pool_name
hash['boottime'] = vm_object.runtime.bootTime if vm_object.runtime && vm_object.runtime.bootTime hash = {
hash['powerstate'] = vm_object.runtime.powerState if vm_object.runtime && vm_object.runtime.powerState 'name' => vm_object.name,
'hostname' => hostname,
'template' => pool_configuration['template'],
'poolname' => pool_name,
'boottime' => boottime,
'powerstate' => powerstate,
}
hash hash
end end
@ -580,24 +577,28 @@ module Vmpooler
available_unit_numbers.sort[0] available_unit_numbers.sort[0]
end end
# Finds the first reference to and returns the folder object for a foldername and an optional datacenter # Finds a folder object by inventory path
# Params: # Params:
# +foldername+:: the folder to find (optionally with / in which case the foldername will be split and each element searched for) # +pool_name+:: the pool to find the folder for
# +connection+:: the vsphere connection object # +connection+:: the vsphere connection object
# +datacentername+:: the datacenter where the folder resides, or nil to return the first datacenter found # returns a ManagedObjectReference for the folder found or nil if not found
# returns a ManagedObjectReference for the first folder found or nil if none found def find_vm_folder(pool_name, connection)
def find_folder(foldername, connection, datacentername) # Find a folder by its inventory path and return the object
datacenter = connection.serviceInstance.find_datacenter(datacentername) # Returns nil when the object found is not a folder
raise("Datacenter #{datacentername} does not exist") if datacenter.nil? pool_configuration = pool_config(pool_name)
base = datacenter.vmFolder return nil if pool_configuration.nil?
folder = pool_configuration['folder']
datacenter = get_target_datacenter_from_config(pool_name)
return nil if datacenter.nil?
folders = foldername.split('/') propSpecs = {
folders.each do |folder| :entity => self,
raise("Unexpected object type encountered (#{base.class}) while finding folder") unless base.is_a? RbVmomi::VIM::Folder :inventoryPath => "#{datacenter}/vm/#{folder}"
base = base.childEntity.find { |f| f.name == folder } }
end
base folder_object = connection.searchIndex.FindByInventoryPath(propSpecs)
return nil unless folder_object.class == RbVmomi::VIM::Folder
folder_object
end end
# Returns an array containing cumulative CPU and memory utilization of a host, and its object reference # Returns an array containing cumulative CPU and memory utilization of a host, and its object reference

View file

@ -97,7 +97,7 @@ EOT
context 'Given a pool folder that is missing' do context 'Given a pool folder that is missing' do
before(:each) do before(:each) do
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(nil) expect(subject).to receive(:find_vm_folder).with(poolname,connection).and_return(nil)
end end
it 'should get a connection' do it 'should get a connection' do
@ -115,7 +115,7 @@ EOT
context 'Given an empty pool folder' do context 'Given an empty pool folder' do
before(:each) do before(:each) do
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object) expect(subject).to receive(:find_vm_folder).with(poolname,connection).and_return(folder_object)
end end
it 'should get a connection' do it 'should get a connection' do
@ -144,7 +144,7 @@ EOT
folder_object.childEntity << mock_vm folder_object.childEntity << mock_vm
end end
expect(subject).to receive(:find_folder).with(pool_config['folder'],connection,datacenter_name).and_return(folder_object) expect(subject).to receive(:find_vm_folder).with(poolname,connection).and_return(folder_object)
end end
it 'should get a connection' do it 'should get a connection' do
@ -190,7 +190,7 @@ EOT
expect(result['name']).to eq(vmname) expect(result['name']).to eq(vmname)
end end
['hostname','template','poolname','boottime','powerstate'].each do |testcase| ['hostname','boottime','powerstate'].each do |testcase|
it "should return nil for #{testcase}" do it "should return nil for #{testcase}" do
result = subject.get_vm(poolname,vmname) result = subject.get_vm(poolname,vmname)
@ -338,11 +338,13 @@ EOT
end end
context 'Given a successful creation' do context 'Given a successful creation' do
let(:folder_object) { mock_RbVmomi_VIM_Folder({ :name => 'pool1'}) }
before(:each) do before(:each) do
template_vm = new_template_object template_vm = new_template_object
allow(subject).to receive(:find_template_vm).and_return(new_template_object) allow(subject).to receive(:find_template_vm).and_return(new_template_object)
allow(template_vm).to receive(:CloneVM_Task).and_return(clone_vm_task) 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) allow(clone_vm_task).to receive(:wait_for_completion).and_return(new_vm_object)
allow(subject).to receive(:find_vm_folder).and_return(folder_object)
end end
it 'should return a hash' do it 'should return a hash' do
@ -1465,106 +1467,54 @@ EOT
end end
end end
describe '#find_folder' do describe '#find_vm_folder' do
let(:foldername) { 'folder'} let(:foldername) { 'folder'}
let(:missing_foldername) { 'missing_folder'}
context 'with no folder hierarchy' do context 'with no folder hierarchy' do
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name }
]
}
}}
it 'should return nil if the folder is not found' do it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(nil)
expect(subject.find_vm_folder(poolname,connection)).to be_nil
end end
end end
context 'with a single layer folder hierarchy' do context 'with a single layer folder hierarchy' do
let(:connection_options) {{ let(:folder_object) { mock_RbVmomi_VIM_Folder({ :name => foldername}) }
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => nil,
foldername => nil,
'folder3' => nil,
}
}
]
}
}}
it 'should return the folder when found' do it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name) allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(folder_object)
expect(result).to_not be_nil allow(folder_object).to receive(:class).and_return(RbVmomi::VIM::Folder)
result = subject.find_vm_folder(poolname,connection)
expect(result.name).to eq(foldername) expect(result.name).to eq(foldername)
end end
it 'should return nil if the folder is not found' do it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(nil)
expect(subject.find_vm_folder(poolname,connection)).to be_nil
end end
end end
context 'with a single layer folder hierarchy in many datacenters' do context 'with a single layer folder hierarchy in many datacenters' do
let(:connection_options) {{ let(:folder_object) { mock_RbVmomi_VIM_Folder({ :name => foldername}) }
: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 it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name) allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(folder_object)
expect(result).to_not be_nil allow(folder_object).to receive(:class).and_return(RbVmomi::VIM::Folder)
result = subject.find_vm_folder(poolname,connection)
expect(result.name).to eq(foldername) expect(result.name).to eq(foldername)
end end
it 'should return nil if the folder is not found' do it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,'AnotherDC')).to be_nil allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(nil)
expect(subject.find_vm_folder(poolname,connection)).to be_nil
end end
end end
context 'with a VM with the same name as a folder in a single layer folder hierarchy' do context 'with a VM with the same name as a folder in a single layer folder hierarchy' do
# The folder hierarchy should include a VM with same name as folder, and appear BEFORE the
# folder in the child list.
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'vm1' => { :object_type => 'vm', :name => foldername },
foldername => nil,
'folder3' => nil,
}
}
]
}
}}
it 'should not return a VM' do it 'should not return a VM' do
pending('https://github.com/puppetlabs/vmpooler/issues/204') pending('https://github.com/puppetlabs/vmpooler/issues/204')
result = subject.find_folder(foldername,connection,datacenter_name) result = subject.find_vm_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil expect(result).to_not be_nil
expect(result.name).to eq(foldername) expect(result.name).to eq(foldername)
expect(result.is_a? RbVmomi::VIM::VirtualMachine).to be false expect(result.is_a? RbVmomi::VIM::VirtualMachine).to be false
@ -1572,77 +1522,19 @@ EOT
end end
context 'with a multi layer folder hierarchy' do context 'with a multi layer folder hierarchy' do
let(:end_folder_name) { 'folder'} let(:foldername) { 'folder2/folder4/folder' }
let(:foldername) { 'folder2/folder4/' + end_folder_name} let(:folder_object) { mock_RbVmomi_VIM_Folder({ :name => foldername}) }
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => {
:children => {
'folder3' => nil,
'folder4' => {
:children => {
end_folder_name => nil,
},
}
},
},
'folder5' => nil,
}
}
]
}
}}
it 'should return the folder when found' do it 'should return the folder when found' do
result = subject.find_folder(foldername,connection,datacenter_name) allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(folder_object)
expect(result).to_not be_nil allow(folder_object).to receive(:class).and_return(RbVmomi::VIM::Folder)
expect(result.name).to eq(end_folder_name) result = subject.find_vm_folder(poolname,connection)
expect(result.name).to eq(foldername)
end end
it 'should return nil if the folder is not found' do it 'should return nil if the folder is not found' do
expect(subject.find_folder(missing_foldername,connection,datacenter_name)).to be_nil allow(connection.searchIndex).to receive(:FindByInventoryPath).and_return(nil)
end expect(subject.find_vm_folder(poolname,connection)).to be_nil
end
context 'with a VM with the same name as a folder in a multi layer folder hierarchy' do
# The folder hierarchy should include a VM with same name as folder mid-hierarchy (i.e. not at the end level)
# and appear BEFORE the folder in the child list.
let(:end_folder_name) { 'folder'}
let(:foldername) { 'folder2/folder4/' + end_folder_name}
let(:connection_options) {{
:serviceContent => {
:datacenters => [
{ :name => datacenter_name,
:vmfolder_tree => {
'folder1' => nil,
'folder2' => {
:children => {
'folder3' => nil,
'vm1' => { :object_type => 'vm', :name => 'folder4' },
'folder4' => {
:children => {
end_folder_name => nil,
},
}
},
},
'folder5' => nil,
}
}
]
}
}}
it 'should not return a VM' do
pending('https://github.com/puppetlabs/vmpooler/issues/204')
result = subject.find_folder(foldername,connection,datacenter_name)
expect(result).to_not be_nil
expect(result.name).to eq(foldername)
expect(result.is_a? RbVmomi::VIM::VirtualMachine,datacenter_name).to be false
end end
end end
end end