(POOLER-52) Modify dummy provider to use a connection pool

Previously a connection pooler class was added.  This commit modifies the Dummy
VM Provider to use a connection pooler.  While the Dummy provider strictly
speaking does not use connections, this allows testing to see what happens when
connection pools are stressed or exhausted.  This commit:
- Modifies functions to use a connection pool object for the public API
  functions
- Modifies the VMPooler YAML with new settings for connection pool size and
  timeout
This commit is contained in:
Glenn Sarti 2017-04-05 10:55:49 -07:00
parent 888ffc4afc
commit 2f37c1e9b5
3 changed files with 222 additions and 172 deletions

View file

@ -19,6 +19,22 @@ module Vmpooler
# duplicate actions to put the @dummylist hashtable into a bad state, for example; # duplicate actions to put the @dummylist hashtable into a bad state, for example;
# Deleting a VM while it's in the middle of adding a disk. # Deleting a VM while it's in the middle of adding a disk.
@write_lock = Mutex.new @write_lock = Mutex.new
# Create a dummy connection pool
connpool_size = provider_config['connection_pool_size'].nil? ? 1 : provider_config['connection_pool_size'].to_i
connpool_timeout = provider_config['connection_pool_timeout'].nil? ? 10 : provider_config['connection_pool_timeout'].to_i
logger.log('d', "[#{name}] ConnPool - Creating a connection pool of size #{connpool_size} with timeout #{connpool_timeout}")
@connection_pool = Vmpooler::PoolManager::GenericConnectionPool.new(
metrics: metrics,
metric_prefix: "#{name}_provider_connection_pool",
size: connpool_size,
timeout: connpool_timeout
) do
# Create a mock connection object
new_conn = { create_timestamp: Time.now, conn_id: rand(2048).to_s }
logger.log('d', "[#{name}] ConnPool - Creating a connection object ID #{new_conn[:conn_id]}")
new_conn
end
end end
def name def name
@ -27,21 +43,30 @@ module Vmpooler
def vms_in_pool(pool_name) def vms_in_pool(pool_name)
vmlist = [] vmlist = []
get_dummy_pool_object(pool_name).each do |vm|
vmlist << { 'name' => vm['name'] } @connection_pool.with_metrics do |_conn|
get_dummy_pool_object(pool_name).each do |vm|
vmlist << { 'name' => vm['name'] }
end
end end
vmlist vmlist
end end
def get_vm_host(pool_name, vm_name) def get_vm_host(pool_name, vm_name)
current_vm = get_dummy_vm(pool_name, vm_name) current_vm = nil
@connection_pool.with_metrics do |_conn|
current_vm = get_dummy_vm(pool_name, vm_name)
end
current_vm.nil? ? raise("VM #{vm_name} does not exist") : current_vm['vm_host'] current_vm.nil? ? raise("VM #{vm_name} does not exist") : current_vm['vm_host']
end end
def find_least_used_compatible_host(pool_name, vm_name) def find_least_used_compatible_host(pool_name, vm_name)
current_vm = get_dummy_vm(pool_name, vm_name) current_vm = nil
@connection_pool.with_metrics do |_conn|
current_vm = get_dummy_vm(pool_name, vm_name)
end
# Unless migratevm_couldmove_percent is specified, don't migrate # Unless migratevm_couldmove_percent is specified, don't migrate
return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil? return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil?
@ -56,64 +81,68 @@ module Vmpooler
end end
def migrate_vm_to_host(pool_name, vm_name, dest_host_name) def migrate_vm_to_host(pool_name, vm_name, dest_host_name)
current_vm = get_dummy_vm(pool_name, vm_name) @connection_pool.with_metrics do |_conn|
# Inject migration delay
unless provider_config['migratevm_max_time'].nil?
migrate_time = 1 + rand(provider_config['migratevm_max_time'])
sleep(migrate_time)
end
# Inject clone failure
unless provider_config['migratevm_fail_percent'].nil?
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['migratevm_fail_percent']
end
@write_lock.synchronize do
current_vm = get_dummy_vm(pool_name, vm_name) current_vm = get_dummy_vm(pool_name, vm_name)
current_vm['vm_host'] = dest_host_name
write_backing_file # Inject migration delay
unless provider_config['migratevm_max_time'].nil?
migrate_time = 1 + rand(provider_config['migratevm_max_time'])
sleep(migrate_time)
end
# Inject clone failure
unless provider_config['migratevm_fail_percent'].nil?
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['migratevm_fail_percent']
end
@write_lock.synchronize do
current_vm = get_dummy_vm(pool_name, vm_name)
current_vm['vm_host'] = dest_host_name
write_backing_file
end
end end
true true
end end
def get_vm(pool_name, vm_name) def get_vm(pool_name, vm_name)
dummy = get_dummy_vm(pool_name, vm_name)
return nil if dummy.nil?
# Randomly power off the VM
unless dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?
if 1 + rand(100) <= provider_config['getvm_poweroff_percent']
@write_lock.synchronize do
dummy = get_dummy_vm(pool_name, vm_name)
dummy['powerstate'] = 'PoweredOff'
write_backing_file
end
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy Powered Off")
end
end
# Randomly rename the host
unless dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?
if 1 + rand(100) <= provider_config['getvm_rename_percent']
@write_lock.synchronize do
dummy = get_dummy_vm(pool_name, vm_name)
dummy['hostname'] = 'DUMMY' + dummy['name']
write_backing_file
end
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy renamed")
end
end
obj = {} obj = {}
obj['name'] = dummy['name'] @connection_pool.with_metrics do |_conn|
obj['hostname'] = dummy['hostname'] dummy = get_dummy_vm(pool_name, vm_name)
obj['boottime'] = dummy['boottime'] return nil if dummy.nil?
obj['template'] = dummy['template']
obj['poolname'] = dummy['poolname'] # Randomly power off the VM
obj['powerstate'] = dummy['powerstate'] unless dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?
obj['snapshots'] = dummy['snapshots'] if 1 + rand(100) <= provider_config['getvm_poweroff_percent']
@write_lock.synchronize do
dummy = get_dummy_vm(pool_name, vm_name)
dummy['powerstate'] = 'PoweredOff'
write_backing_file
end
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy Powered Off")
end
end
# Randomly rename the host
unless dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?
if 1 + rand(100) <= provider_config['getvm_rename_percent']
@write_lock.synchronize do
dummy = get_dummy_vm(pool_name, vm_name)
dummy['hostname'] = 'DUMMY' + dummy['name']
write_backing_file
end
logger.log('d', "[ ] [#{dummy['poolname']}] '#{dummy['name']}' is being Dummy renamed")
end
end
obj['name'] = dummy['name']
obj['hostname'] = dummy['hostname']
obj['boottime'] = dummy['boottime']
obj['template'] = dummy['template']
obj['poolname'] = dummy['poolname']
obj['powerstate'] = dummy['powerstate']
obj['snapshots'] = dummy['snapshots']
end
obj obj
end end
@ -150,172 +179,185 @@ module Vmpooler
logger.log('d', "[ ] [#{pool_name}] '#{dummy_hostname}' is being cloned from '#{template_name}'") logger.log('d', "[ ] [#{pool_name}] '#{dummy_hostname}' is being cloned from '#{template_name}'")
# Inject clone time delay @connection_pool.with_metrics do |_conn|
unless provider_config['createvm_max_time'].nil? # Inject clone time delay
@write_lock.synchronize do unless provider_config['createvm_max_time'].nil?
vm['dummy_state'] = 'CLONING' @write_lock.synchronize do
write_backing_file vm['dummy_state'] = 'CLONING'
end write_backing_file
clone_time = 1 + rand(provider_config['createvm_max_time']) end
sleep(clone_time) clone_time = 1 + rand(provider_config['createvm_max_time'])
end sleep(clone_time)
begin
# Inject clone failure
unless provider_config['createvm_fail_percent'].nil?
raise('Dummy Failure for createvm_fail_percent') if 1 + rand(100) <= provider_config['createvm_fail_percent']
end end
# Assert the VM is ready for use begin
@write_lock.synchronize do # Inject clone failure
vm['dummy_state'] = 'RUNNING' unless provider_config['createvm_fail_percent'].nil?
write_backing_file raise('Dummy Failure for createvm_fail_percent') if 1 + rand(100) <= provider_config['createvm_fail_percent']
end
# Assert the VM is ready for use
@write_lock.synchronize do
vm['dummy_state'] = 'RUNNING'
write_backing_file
end
rescue => _err
@write_lock.synchronize do
remove_dummy_vm(pool_name, dummy_hostname)
write_backing_file
end
raise
end end
rescue => _err
@write_lock.synchronize do
remove_dummy_vm(pool_name, dummy_hostname)
write_backing_file
end
raise
end end
get_vm(pool_name, dummy_hostname) get_vm(pool_name, dummy_hostname)
end end
def create_disk(pool_name, vm_name, disk_size) def create_disk(pool_name, vm_name, disk_size)
vm_object = get_dummy_vm(pool_name, vm_name) @connection_pool.with_metrics do |_conn|
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
# Inject create time delay
unless provider_config['createdisk_max_time'].nil?
delay = 1 + rand(provider_config['createdisk_max_time'])
sleep(delay)
end
# Inject create failure
unless provider_config['createdisk_fail_percent'].nil?
raise('Dummy Failure for createdisk_fail_percent') if 1 + rand(100) <= provider_config['createdisk_fail_percent']
end
@write_lock.synchronize do
vm_object = get_dummy_vm(pool_name, vm_name) vm_object = get_dummy_vm(pool_name, vm_name)
vm_object['disks'] << disk_size raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
write_backing_file
# Inject create time delay
unless provider_config['createdisk_max_time'].nil?
delay = 1 + rand(provider_config['createdisk_max_time'])
sleep(delay)
end
# Inject create failure
unless provider_config['createdisk_fail_percent'].nil?
raise('Dummy Failure for createdisk_fail_percent') if 1 + rand(100) <= provider_config['createdisk_fail_percent']
end
@write_lock.synchronize do
vm_object = get_dummy_vm(pool_name, vm_name)
vm_object['disks'] << disk_size
write_backing_file
end
end end
true true
end end
def create_snapshot(pool_name, vm_name, snapshot_name) def create_snapshot(pool_name, vm_name, snapshot_name)
vm_object = get_dummy_vm(pool_name, vm_name) @connection_pool.with_metrics do |_conn|
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
# Inject create time delay
unless provider_config['createsnapshot_max_time'].nil?
delay = 1 + rand(provider_config['createsnapshot_max_time'])
sleep(delay)
end
# Inject create failure
unless provider_config['createsnapshot_fail_percent'].nil?
raise('Dummy Failure for createsnapshot_fail_percent') if 1 + rand(100) <= provider_config['createsnapshot_fail_percent']
end
@write_lock.synchronize do
vm_object = get_dummy_vm(pool_name, vm_name) vm_object = get_dummy_vm(pool_name, vm_name)
vm_object['snapshots'] << snapshot_name raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
write_backing_file
# Inject create time delay
unless provider_config['createsnapshot_max_time'].nil?
delay = 1 + rand(provider_config['createsnapshot_max_time'])
sleep(delay)
end
# Inject create failure
unless provider_config['createsnapshot_fail_percent'].nil?
raise('Dummy Failure for createsnapshot_fail_percent') if 1 + rand(100) <= provider_config['createsnapshot_fail_percent']
end
@write_lock.synchronize do
vm_object = get_dummy_vm(pool_name, vm_name)
vm_object['snapshots'] << snapshot_name
write_backing_file
end
end end
true true
end end
def revert_snapshot(pool_name, vm_name, snapshot_name) def revert_snapshot(pool_name, vm_name, snapshot_name)
vm_object = get_dummy_vm(pool_name, vm_name) vm_object = nil
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil? @connection_pool.with_metrics do |_conn|
vm_object = get_dummy_vm(pool_name, vm_name)
raise("VM #{vm_name} does not exist in Pool #{pool_name} for the provider #{name}") if vm_object.nil?
# Inject create time delay # Inject create time delay
unless provider_config['revertsnapshot_max_time'].nil? unless provider_config['revertsnapshot_max_time'].nil?
delay = 1 + rand(provider_config['revertsnapshot_max_time']) delay = 1 + rand(provider_config['revertsnapshot_max_time'])
sleep(delay) sleep(delay)
end end
# Inject create failure # Inject create failure
unless provider_config['revertsnapshot_fail_percent'].nil? unless provider_config['revertsnapshot_fail_percent'].nil?
raise('Dummy Failure for revertsnapshot_fail_percent') if 1 + rand(100) <= provider_config['revertsnapshot_fail_percent'] raise('Dummy Failure for revertsnapshot_fail_percent') if 1 + rand(100) <= provider_config['revertsnapshot_fail_percent']
end
end end
vm_object['snapshots'].include?(snapshot_name) vm_object['snapshots'].include?(snapshot_name)
end end
def destroy_vm(pool_name, vm_name) def destroy_vm(pool_name, vm_name)
vm = get_dummy_vm(pool_name, vm_name) @connection_pool.with_metrics do |_conn|
return false if vm.nil? vm = get_dummy_vm(pool_name, vm_name)
return false if vm['poolname'] != pool_name return false if vm.nil?
return false if vm['poolname'] != pool_name
# Shutdown down the VM if it's poweredOn # Shutdown down the VM if it's poweredOn
if vm['powerstate'] == 'PoweredOn' if vm['powerstate'] == 'PoweredOn'
logger.log('d', "[ ] [#{pool_name}] '#{vm_name}' is being shut down") logger.log('d', "[ ] [#{pool_name}] '#{vm_name}' is being shut down")
# Inject shutdown delay time # Inject shutdown delay time
unless provider_config['destroyvm_max_shutdown_time'].nil? unless provider_config['destroyvm_max_shutdown_time'].nil?
shutdown_time = 1 + rand(provider_config['destroyvm_max_shutdown_time']) shutdown_time = 1 + rand(provider_config['destroyvm_max_shutdown_time'])
sleep(shutdown_time) sleep(shutdown_time)
end
@write_lock.synchronize do
vm = get_dummy_vm(pool_name, vm_name)
vm['powerstate'] = 'PoweredOff'
write_backing_file
end
end end
# Inject destroy VM delay
unless provider_config['destroyvm_max_time'].nil?
destroy_time = 1 + rand(provider_config['destroyvm_max_time'])
sleep(destroy_time)
end
# Inject destroy VM failure
unless provider_config['destroyvm_fail_percent'].nil?
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['destroyvm_fail_percent']
end
# 'Destroy' the VM
@write_lock.synchronize do @write_lock.synchronize do
vm = get_dummy_vm(pool_name, vm_name) remove_dummy_vm(pool_name, vm_name)
vm['powerstate'] = 'PoweredOff'
write_backing_file write_backing_file
end end
end end
# Inject destroy VM delay
unless provider_config['destroyvm_max_time'].nil?
destroy_time = 1 + rand(provider_config['destroyvm_max_time'])
sleep(destroy_time)
end
# Inject destroy VM failure
unless provider_config['destroyvm_fail_percent'].nil?
raise('Dummy Failure for migratevm_fail_percent') if 1 + rand(100) <= provider_config['destroyvm_fail_percent']
end
# 'Destroy' the VM
@write_lock.synchronize do
remove_dummy_vm(pool_name, vm_name)
write_backing_file
end
true true
end end
def vm_ready?(pool_name, vm_name) def vm_ready?(pool_name, vm_name)
vm_object = get_dummy_vm(pool_name, vm_name) @connection_pool.with_metrics do |_conn|
return false if vm_object.nil? vm_object = get_dummy_vm(pool_name, vm_name)
return false if vm_object['poolname'] != pool_name return false if vm_object.nil?
return true if vm_object['ready'] return false if vm_object['poolname'] != pool_name
return true if vm_object['ready']
timeout = provider_config['is_ready_timeout'] || 5 timeout = provider_config['is_ready_timeout'] || 5
Timeout.timeout(timeout) do Timeout.timeout(timeout) do
while vm_object['dummy_state'] != 'RUNNING' while vm_object['dummy_state'] != 'RUNNING'
sleep(2) sleep(2)
vm_object = get_dummy_vm(pool_name, vm_name) vm_object = get_dummy_vm(pool_name, vm_name)
end
end end
end
# Simulate how long it takes from a VM being powered on until # Simulate how long it takes from a VM being powered on until
# it's ready to receive a connection # it's ready to receive a connection
sleep(2) sleep(2)
unless provider_config['vmready_fail_percent'].nil? unless provider_config['vmready_fail_percent'].nil?
raise('Dummy Failure for vmready_fail_percent') if 1 + rand(100) <= provider_config['vmready_fail_percent'] raise('Dummy Failure for vmready_fail_percent') if 1 + rand(100) <= provider_config['vmready_fail_percent']
end end
@write_lock.synchronize do @write_lock.synchronize do
vm_object['ready'] = true vm_object['ready'] = true
write_backing_file write_backing_file
end
end end
true true

View file

@ -76,11 +76,11 @@ describe 'Vmpooler::PoolManager::Provider::Dummy' do
let(:config) { YAML.load(<<-EOT let(:config) { YAML.load(<<-EOT
--- ---
:config: :config:
max_tries: 3
retry_factor: 10
:providers: :providers:
:dummy: :dummy:
key1: 'value1' key1: 'value1'
# Drop the connection pool timeout way down for spec tests so they fail fast
connection_pool_timeout: 1
:pools: :pools:
- name: '#{pool_name}' - name: '#{pool_name}'
size: 5 size: 5

View file

@ -50,6 +50,14 @@
# The filename used to store the backing text file. If this is not specified the VM state is only # The filename used to store the backing text file. If this is not specified the VM state is only
# kept in memory, and is lost when the Provider is shutdown # kept in memory, and is lost when the Provider is shutdown
# #
# - connection_pool_size (Optional)
# The size of the dummy connection pool. This can be used to simulate constrained provider resources e.g. 200 pools sharing on connection
# (optional; default 1)
#
# - connection_pool_timeout (Optional)
# The number of seconds to wait for a connection object from the pool. If the timeout is exceeded an error is raised
# (optional; default 10 seconds)
#
# - migratevm_couldmove_percent # - migratevm_couldmove_percent
# Percent chance that a VM could be moved to another host # Percent chance that a VM could be moved to another host
# (optional; default 0%) # (optional; default 0%)