diff --git a/lib/vmpooler/providers/dummy.rb b/lib/vmpooler/providers/dummy.rb index 2b124a3..1173511 100644 --- a/lib/vmpooler/providers/dummy.rb +++ b/lib/vmpooler/providers/dummy.rb @@ -19,6 +19,22 @@ module Vmpooler # 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. @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 def name @@ -27,21 +43,30 @@ module Vmpooler def vms_in_pool(pool_name) 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 vmlist end 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'] end 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 return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil? @@ -56,64 +81,68 @@ module Vmpooler end def migrate_vm_to_host(pool_name, vm_name, dest_host_name) - current_vm = get_dummy_vm(pool_name, vm_name) - - # 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 + @connection_pool.with_metrics do |_conn| 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 true end 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['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'] + @connection_pool.with_metrics do |_conn| + 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['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 end @@ -150,172 +179,185 @@ module Vmpooler logger.log('d', "[ ] [#{pool_name}] '#{dummy_hostname}' is being cloned from '#{template_name}'") - # Inject clone time delay - unless provider_config['createvm_max_time'].nil? - @write_lock.synchronize do - vm['dummy_state'] = 'CLONING' - write_backing_file - end - clone_time = 1 + rand(provider_config['createvm_max_time']) - sleep(clone_time) - end - - 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'] + @connection_pool.with_metrics do |_conn| + # Inject clone time delay + unless provider_config['createvm_max_time'].nil? + @write_lock.synchronize do + vm['dummy_state'] = 'CLONING' + write_backing_file + end + clone_time = 1 + rand(provider_config['createvm_max_time']) + sleep(clone_time) end - # Assert the VM is ready for use - @write_lock.synchronize do - vm['dummy_state'] = 'RUNNING' - write_backing_file + 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 + + # 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 - rescue => _err - @write_lock.synchronize do - remove_dummy_vm(pool_name, dummy_hostname) - write_backing_file - end - raise end get_vm(pool_name, dummy_hostname) end def create_disk(pool_name, vm_name, disk_size) - 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 - 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 + @connection_pool.with_metrics do |_conn| vm_object = get_dummy_vm(pool_name, vm_name) - vm_object['disks'] << disk_size - write_backing_file + 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['disks'] << disk_size + write_backing_file + end end true end def create_snapshot(pool_name, vm_name, snapshot_name) - 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 - 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 + @connection_pool.with_metrics do |_conn| vm_object = get_dummy_vm(pool_name, vm_name) - vm_object['snapshots'] << snapshot_name - write_backing_file + 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['snapshots'] << snapshot_name + write_backing_file + end end true end def revert_snapshot(pool_name, vm_name, snapshot_name) - 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? + 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 - unless provider_config['revertsnapshot_max_time'].nil? - delay = 1 + rand(provider_config['revertsnapshot_max_time']) - sleep(delay) - end + # Inject create time delay + unless provider_config['revertsnapshot_max_time'].nil? + delay = 1 + rand(provider_config['revertsnapshot_max_time']) + sleep(delay) + end - # Inject create failure - unless provider_config['revertsnapshot_fail_percent'].nil? - raise('Dummy Failure for revertsnapshot_fail_percent') if 1 + rand(100) <= provider_config['revertsnapshot_fail_percent'] + # Inject create failure + unless provider_config['revertsnapshot_fail_percent'].nil? + raise('Dummy Failure for revertsnapshot_fail_percent') if 1 + rand(100) <= provider_config['revertsnapshot_fail_percent'] + end end vm_object['snapshots'].include?(snapshot_name) end def destroy_vm(pool_name, vm_name) - vm = get_dummy_vm(pool_name, vm_name) - return false if vm.nil? - return false if vm['poolname'] != pool_name + @connection_pool.with_metrics do |_conn| + vm = get_dummy_vm(pool_name, vm_name) + return false if vm.nil? + return false if vm['poolname'] != pool_name - # Shutdown down the VM if it's poweredOn - if vm['powerstate'] == 'PoweredOn' - logger.log('d', "[ ] [#{pool_name}] '#{vm_name}' is being shut down") + # Shutdown down the VM if it's poweredOn + if vm['powerstate'] == 'PoweredOn' + logger.log('d', "[ ] [#{pool_name}] '#{vm_name}' is being shut down") - # Inject shutdown delay time - unless provider_config['destroyvm_max_shutdown_time'].nil? - shutdown_time = 1 + rand(provider_config['destroyvm_max_shutdown_time']) - sleep(shutdown_time) + # Inject shutdown delay time + unless provider_config['destroyvm_max_shutdown_time'].nil? + shutdown_time = 1 + rand(provider_config['destroyvm_max_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 + # 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 - vm = get_dummy_vm(pool_name, vm_name) - vm['powerstate'] = 'PoweredOff' + remove_dummy_vm(pool_name, vm_name) write_backing_file 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 end def vm_ready?(pool_name, vm_name) - vm_object = get_dummy_vm(pool_name, vm_name) - return false if vm_object.nil? - return false if vm_object['poolname'] != pool_name - return true if vm_object['ready'] + @connection_pool.with_metrics do |_conn| + vm_object = get_dummy_vm(pool_name, vm_name) + return false if vm_object.nil? + 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 - while vm_object['dummy_state'] != 'RUNNING' - sleep(2) - vm_object = get_dummy_vm(pool_name, vm_name) + Timeout.timeout(timeout) do + while vm_object['dummy_state'] != 'RUNNING' + sleep(2) + vm_object = get_dummy_vm(pool_name, vm_name) + end end - end - # Simulate how long it takes from a VM being powered on until - # it's ready to receive a connection - sleep(2) + # Simulate how long it takes from a VM being powered on until + # it's ready to receive a connection + sleep(2) - unless provider_config['vmready_fail_percent'].nil? - raise('Dummy Failure for vmready_fail_percent') if 1 + rand(100) <= provider_config['vmready_fail_percent'] - end + unless provider_config['vmready_fail_percent'].nil? + raise('Dummy Failure for vmready_fail_percent') if 1 + rand(100) <= provider_config['vmready_fail_percent'] + end - @write_lock.synchronize do - vm_object['ready'] = true - write_backing_file + @write_lock.synchronize do + vm_object['ready'] = true + write_backing_file + end end true diff --git a/spec/unit/providers/dummy_spec.rb b/spec/unit/providers/dummy_spec.rb index 5baf092..f0156f9 100644 --- a/spec/unit/providers/dummy_spec.rb +++ b/spec/unit/providers/dummy_spec.rb @@ -76,11 +76,11 @@ describe 'Vmpooler::PoolManager::Provider::Dummy' do let(:config) { YAML.load(<<-EOT --- :config: - max_tries: 3 - retry_factor: 10 :providers: :dummy: key1: 'value1' + # Drop the connection pool timeout way down for spec tests so they fail fast + connection_pool_timeout: 1 :pools: - name: '#{pool_name}' size: 5 diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example index b7d3ea6..86fd852 100644 --- a/vmpooler.yaml.example +++ b/vmpooler.yaml.example @@ -50,6 +50,14 @@ # 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 # +# - 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 # Percent chance that a VM could be moved to another host # (optional; default 0%)