diff --git a/lib/vmpooler/providers.rb b/lib/vmpooler/providers.rb index f640163..c1c2071 100644 --- a/lib/vmpooler/providers.rb +++ b/lib/vmpooler/providers.rb @@ -1,4 +1,4 @@ -%w(base vsphere).each do |lib| +%w(base dummy vsphere).each do |lib| begin require "vmpooler/providers/#{lib}" rescue LoadError diff --git a/lib/vmpooler/providers/dummy.rb b/lib/vmpooler/providers/dummy.rb new file mode 100644 index 0000000..2b124a3 --- /dev/null +++ b/lib/vmpooler/providers/dummy.rb @@ -0,0 +1,360 @@ +require 'yaml' + +module Vmpooler + class PoolManager + class Provider + class Dummy < Vmpooler::PoolManager::Provider::Base + # Fake VM Provider for testing + + def initialize(config, logger, metrics, name, options) + super(config, logger, metrics, name, options) + dummyfilename = provider_config['filename'] + + # This initial_state option is only intended to be used by spec tests + @dummylist = provider_options['initial_state'].nil? ? {} : provider_options['initial_state'] + + @dummylist = YAML.load_file(dummyfilename) if !dummyfilename.nil? && File.exist?(dummyfilename) + + # Even though this code is using Mutexes, it's still no 100% atomic i.e. it's still possible for + # 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 + end + + def name + 'dummy' + end + + def vms_in_pool(pool_name) + vmlist = [] + get_dummy_pool_object(pool_name).each do |vm| + vmlist << { 'name' => vm['name'] } + end + + vmlist + end + + def get_vm_host(pool_name, vm_name) + current_vm = get_dummy_vm(pool_name, vm_name) + + 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) + + # Unless migratevm_couldmove_percent is specified, don't migrate + return current_vm['vm_host'] if provider_config['migratevm_couldmove_percent'].nil? + + # Only migrate if migratevm_couldmove_percent is met + return current_vm['vm_host'] if 1 + rand(100) > provider_config['migratevm_couldmove_percent'] + + # Simulate a 10 node cluster and randomly pick a different one + new_host = 'HOST' + (1 + rand(10)).to_s while new_host == current_vm['vm_host'] + + new_host + 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 + current_vm = get_dummy_vm(pool_name, vm_name) + current_vm['vm_host'] = dest_host_name + write_backing_file + 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'] + + obj + end + + def create_vm(pool_name, dummy_hostname) + pool = pool_config(pool_name) + raise("Pool #{pool_name} does not exist for the provider #{name}") if pool.nil? + + template_name = pool['template'] + + vm = {} + vm['name'] = dummy_hostname + vm['hostname'] = dummy_hostname + vm['domain'] = 'dummy.local' + # 'vm_template' is the name of the template to use to clone the VM from <----- Do we need this?!?!? + vm['vm_template'] = template_name + # 'template' is the Template name in VM Pooler API, in our case that's the poolname. + vm['template'] = pool_name + vm['poolname'] = pool_name + vm['ready'] = false + vm['boottime'] = Time.now + vm['powerstate'] = 'PoweredOn' + vm['vm_host'] = 'HOST1' + vm['dummy_state'] = 'UNKNOWN' + vm['snapshots'] = [] + vm['disks'] = [] + + # Make sure the pool exists in the dummy list + @write_lock.synchronize do + get_dummy_pool_object(pool_name) + @dummylist['pool'][pool_name] << vm + write_backing_file + end + + 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'] + 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 + + 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 + vm_object = get_dummy_vm(pool_name, vm_name) + vm_object['disks'] << disk_size + write_backing_file + 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 + vm_object = get_dummy_vm(pool_name, vm_name) + vm_object['snapshots'] << snapshot_name + write_backing_file + 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? + + # 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'] + 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 + + # 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) + 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 + 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'] + + 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) + end + end + + # 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 + + @write_lock.synchronize do + vm_object['ready'] = true + write_backing_file + end + + true + end + + private + + # Note - NEVER EVER use the @write_lock object in the private methods!!!! Deadlocks will ensue + + def remove_dummy_vm(pool_name, vm_name) + return if @dummylist['pool'][pool_name].nil? + new_poollist = @dummylist['pool'][pool_name].delete_if { |vm| vm['name'] == vm_name } + @dummylist['pool'][pool_name] = new_poollist + end + + # Get's the pool config safely from the in-memory hashtable + def get_dummy_pool_object(pool_name) + @dummylist['pool'] = {} if @dummylist['pool'].nil? + @dummylist['pool'][pool_name] = [] if @dummylist['pool'][pool_name].nil? + + @dummylist['pool'][pool_name] + end + + def get_dummy_vm(pool_name, vm_name) + return nil if @dummylist['pool'][pool_name].nil? + + @dummylist['pool'][pool_name].each do |poolvm| + return poolvm if poolvm['name'] == vm_name + end + + nil + end + + def write_backing_file + dummyfilename = provider_config['filename'] + return if dummyfilename.nil? + File.open(dummyfilename, 'w') { |file| file.write(YAML.dump(@dummylist)) } + end + end + end + end +end diff --git a/spec/unit/providers/dummy_spec.rb b/spec/unit/providers/dummy_spec.rb new file mode 100644 index 0000000..5baf092 --- /dev/null +++ b/spec/unit/providers/dummy_spec.rb @@ -0,0 +1,509 @@ +require 'spec_helper' + +describe 'Vmpooler::PoolManager::Provider::Dummy' do + let(:logger) { MockLogger.new } + let(:metrics) { Vmpooler::DummyStatsd.new } + let(:pool_name) { 'pool1' } + let(:other_pool_name) { 'pool2' } + let(:vm_name) { 'vm1' } + + let(:running_vm_name) { 'vm2' } + let(:notready_vm_name) { 'vm3' } + + let (:provider_options) { + # Construct an initial state for testing + dummylist = {} + dummylist['pool'] = {} + # pool1 is a pool of "normal" VMs + dummylist['pool'][pool_name] = [] + # A normal running VM + vm = {} + vm['name'] = vm_name + vm['hostname'] = vm_name + vm['domain'] = 'dummy.local' + vm['vm_template'] = 'template1' + vm['template'] = pool_name + vm['poolname'] = pool_name + vm['ready'] = true + vm['boottime'] = Time.now + vm['powerstate'] = 'PoweredOn' + vm['vm_host'] = 'HOST1' + vm['snapshots'] = [] + vm['disks'] = [] + vm['dummy_state'] = 'RUNNING' + dummylist['pool'][pool_name] << vm + + # pool2 is a pool of "abnormal" VMs e.g. PoweredOff etc. + dummylist['pool'][other_pool_name] = [] + # A freshly provisioned VM that is not ready + vm = {} + vm['name'] = running_vm_name + vm['hostname'] = running_vm_name + vm['domain'] = 'dummy.local' + vm['vm_template'] = 'template1' + vm['template'] = other_pool_name + vm['poolname'] = other_pool_name + vm['ready'] = false + vm['boottime'] = Time.now + vm['powerstate'] = 'PoweredOn' + vm['vm_host'] = 'HOST1' + vm['snapshots'] = [] + vm['disks'] = [] + vm['dummy_state'] = 'UNKNOWN' + dummylist['pool'][other_pool_name] << vm + # A freshly provisioned VM that is running but not ready + vm = {} + vm['name'] = notready_vm_name + vm['hostname'] = notready_vm_name + vm['domain'] = 'dummy.local' + vm['vm_template'] = 'template1' + vm['template'] = other_pool_name + vm['poolname'] = other_pool_name + vm['ready'] = false + vm['boottime'] = Time.now + vm['powerstate'] = 'PoweredOn' + vm['vm_host'] = 'HOST1' + vm['snapshots'] = [] + vm['disks'] = [] + vm['dummy_state'] = 'RUNNING' + dummylist['pool'][other_pool_name] << vm + + { + 'initial_state' => dummylist + } + } + + let(:config) { YAML.load(<<-EOT +--- +:config: + max_tries: 3 + retry_factor: 10 +:providers: + :dummy: + key1: 'value1' +:pools: + - name: '#{pool_name}' + size: 5 + - name: 'pool2' + size: 5 +EOT + ) + } + + subject { Vmpooler::PoolManager::Provider::Dummy.new(config, logger, metrics, 'dummy', provider_options) } + + describe '#name' do + it 'should be dummy' do + expect(subject.name).to eq('dummy') + end + end + + describe '#vms_in_pool' do + it 'should return [] when pool does not exist' do + vm_list = subject.vms_in_pool('missing_pool') + + expect(vm_list).to eq([]) + end + + it 'should return an array of VMs when pool exists' do + vm_list = subject.vms_in_pool(pool_name) + + expect(vm_list.count).to eq(1) + end + end + + describe '#get_vm_host' do + it 'should return the hostname when VM exists' do + expect(subject.get_vm_host(pool_name, vm_name)).to eq('HOST1') + end + + it 'should error when VM does not exist' do + expect{subject.get_vm_host(pool_name, 'doesnotexist')}.to raise_error(RuntimeError) + end + end + + describe '#find_least_used_compatible_host' do + it 'should return the current host' do + new_host = subject.find_least_used_compatible_host(pool_name, vm_name) + expect(new_host).to eq('HOST1') + end + + context 'using migratevm_couldmove_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['migratevm_couldmove_percent'] = 0 + end + + it 'should return the current host' do + new_host = subject.find_least_used_compatible_host(pool_name, vm_name) + expect(new_host).to eq('HOST1') + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['migratevm_couldmove_percent'] = 100 + end + + it 'should return a different host' do + new_host = subject.find_least_used_compatible_host(pool_name, vm_name) + expect(new_host).to_not eq('HOST1') + end + end + + end + end + + describe '#migrate_vm_to_host' do + it 'should move to the new host' do + expect(subject.migrate_vm_to_host(pool_name, 'vm1','NEWHOST')).to eq(true) + expect(subject.get_vm_host(pool_name, 'vm1')).to eq('NEWHOST') + end + + context 'using migratevm_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['migratevm_fail_percent'] = 0 + end + + it 'should move to the new host' do + expect(subject.migrate_vm_to_host(pool_name, 'vm1','NEWHOST')).to eq(true) + expect(subject.get_vm_host(pool_name, 'vm1')).to eq('NEWHOST') + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['migratevm_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{subject.migrate_vm_to_host(pool_name, 'vm1','NEWHOST')}.to raise_error(/migratevm_fail_percent/) + end + end + end + end + + describe '#get_vm' do + it 'should return the VM when VM exists' do + vm = subject.get_vm(pool_name, vm_name) + expect(vm['name']).to eq(vm_name) + expect(vm['powerstate']).to eq('PoweredOn') + expect(vm['hostname']).to eq(vm['name']) + end + + it 'should return nil when VM does not exist' do + expect(subject.get_vm(pool_name, 'doesnotexist')).to eq(nil) + end + + context 'using getvm_poweroff_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['getvm_poweroff_percent'] = 0 + end + + it 'will not power off a VM' do + vm = subject.get_vm(pool_name, vm_name) + expect(vm['name']).to eq(vm_name) + expect(vm['powerstate']).to eq('PoweredOn') + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['getvm_poweroff_percent'] = 100 + end + + it 'will power off a VM' do + vm = subject.get_vm(pool_name, vm_name) + expect(vm['name']).to eq(vm_name) + expect(vm['powerstate']).to eq('PoweredOff') + end + end + end + + context 'using getvm_rename_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['getvm_rename_percent'] = 0 + end + + it 'will not rename a VM' do + vm = subject.get_vm(pool_name, vm_name) + expect(vm['name']).to eq(vm_name) + expect(vm['hostname']).to eq(vm['name']) + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['getvm_rename_percent'] = 100 + end + + it 'will rename a VM' do + vm = subject.get_vm(pool_name, vm_name) + expect(vm['name']).to eq(vm_name) + expect(vm['hostname']).to_not eq(vm['name']) + end + end + end + end + + describe '#create_vm' do + let(:new_vm_name) { 'newvm' } + + it 'should return a new VM' do + expect(subject.create_vm(pool_name, new_vm_name)['name']).to eq(new_vm_name) + end + + it 'should increase the number of VMs in the pool' do + old_pool_count = subject.vms_in_pool(pool_name).count + + new_vm = subject.create_vm(pool_name, new_vm_name) + + expect(subject.vms_in_pool(pool_name).count).to eq(old_pool_count + 1) + end + + context 'using createvm_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['createvm_fail_percent'] = 0 + end + + it 'should return a new VM' do + expect(subject.create_vm(pool_name, new_vm_name)['name']).to eq(new_vm_name) + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['createvm_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{subject.create_vm(pool_name, new_vm_name)}.to raise_error(/createvm_fail_percent/) + end + + it 'new VM should not exist' do + begin + subject.create_vm(pool_name, new_vm_name) + rescue + end + expect(subject.get_vm(pool_name, new_vm_name)).to eq(nil) + end + end + end + end + + describe '#create_disk' do + let(:disk_size) { 10 } + + it 'should return true when the disk is created' do + expect(subject.create_disk(pool_name, vm_name,disk_size)).to be true + end + + it 'should raise an error when VM does not exist' do + expect{ subject.create_disk(pool_name, 'doesnotexist',disk_size) }.to raise_error(/VM doesnotexist does not exist/) + end + + context 'using createdisk_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['createdisk_fail_percent'] = 0 + end + + it 'should return true when the disk is created' do + expect(subject.create_disk(pool_name, vm_name,disk_size)).to be true + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['createdisk_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{subject.create_disk(pool_name, vm_name,disk_size)}.to raise_error(/createdisk_fail_percent/) + end + end + end + end + + describe '#create_snapshot' do + let(:snapshot_name) { 'newsnapshot' } + + it 'should return true when the snapshot is created' do + expect(subject.create_snapshot(pool_name, vm_name, snapshot_name)).to be true + end + + it 'should raise an error when VM does not exist' do + expect{ subject.create_snapshot(pool_name, 'doesnotexist', snapshot_name) }.to raise_error(/VM doesnotexist does not exist/) + end + + context 'using createsnapshot_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['createsnapshot_fail_percent'] = 0 + end + + it 'should return true when the disk is created' do + expect(subject.create_snapshot(pool_name, vm_name, snapshot_name)).to be true + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['createsnapshot_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{ subject.create_snapshot(pool_name, vm_name, snapshot_name) }.to raise_error(/createsnapshot_fail_percent/) + end + end + end + end + + describe '#revert_snapshot' do + let(:snapshot_name) { 'newsnapshot' } + + before(:each) do + # Create a snapshot + subject.create_snapshot(pool_name, vm_name, snapshot_name) + end + + it 'should return true when the snapshot is reverted' do + expect(subject.revert_snapshot(pool_name, vm_name, snapshot_name)).to be true + end + + it 'should raise an error when VM does not exist' do + expect{ subject.revert_snapshot(pool_name, 'doesnotexist', snapshot_name) }.to raise_error(/VM doesnotexist does not exist/) + end + + it 'should return false when the snapshot does not exist' do + expect(subject.revert_snapshot(pool_name, vm_name, 'doesnotexist')).to be false + end + + context 'using revertsnapshot_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['revertsnapshot_fail_percent'] = 0 + end + + it 'should return true when the snapshot is reverted' do + expect(subject.revert_snapshot(pool_name, vm_name, snapshot_name)).to be true + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['revertsnapshot_fail_percent'] = 100 + end + + it 'should raise an error when VM does not exist' do + expect{ subject.revert_snapshot(pool_name, vm_name, snapshot_name) }.to raise_error(/revertsnapshot_fail_percent/) + end + end + end + end + + describe '#destroy_vm' do + it 'should return true when destroyed' do + expect(subject.destroy_vm(pool_name, vm_name)).to eq(true) + end + + it 'should log if the VM is powered off' do + allow(logger).to receive(:log) + expect(logger).to receive(:log).with('d', "[ ] [pool1] 'vm1' is being shut down") + expect(subject.destroy_vm(pool_name, vm_name)).to eq(true) + end + + it 'should return false if VM does not exist' do + expect(subject.destroy_vm('doesnotexist',vm_name)).to eq(false) + end + + it 'should return false if VM is not in the correct pool' do + expect(subject.destroy_vm(other_pool_name, vm_name)).to eq(false) + end + + context 'using destroyvm_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['destroyvm_fail_percent'] = 0 + end + + it 'should return true when destroyed' do + expect(subject.destroy_vm(pool_name, vm_name)).to eq(true) + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['destroyvm_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{subject.destroy_vm(pool_name, vm_name)}.to raise_error(/migratevm_fail_percent/) + end + end + end + end + + describe '#vm_ready?' do + before(:each) do + # Speed up tests and ignore sleeping + allow(subject).to receive(:sleep) + end + + it 'should return true if ready' do + expect(subject.vm_ready?(pool_name, vm_name)).to eq(true) + end + + it 'should return false if VM does not exist' do + expect(subject.vm_ready?(pool_name, 'doesnotexist')).to eq(false) + end + + it 'should return false if VM is not in the correct pool' do + expect(subject.vm_ready?(other_pool_name, vm_name)).to eq(false) + end + + it 'should raise an error if timeout expires' do + expect{subject.vm_ready?(other_pool_name, running_vm_name)}.to raise_error(Timeout::Error) + end + + it 'should return true if VM becomes ready' do + expect(subject.vm_ready?(other_pool_name, notready_vm_name)).to eq(true) + end + + context 'using vmready_fail_percent' do + describe 'of zero' do + before(:each) do + config[:providers][:dummy]['vmready_fail_percent'] = 0 + end + + it 'should return true if VM becomes ready' do + expect(subject.vm_ready?(other_pool_name, notready_vm_name)).to eq(true) + end + end + + describe 'of 100' do + before(:each) do + config[:providers][:dummy]['vmready_fail_percent'] = 100 + end + + it 'should raise an error' do + expect{subject.vm_ready?(other_pool_name, notready_vm_name)}.to raise_error(/vmready_fail_percent/) + end + end + end + end + + describe '#vm_exists?' do + it 'should return true when VM exists' do + expect(subject.vm_exists?(pool_name, vm_name)).to eq(true) + end + + it 'should return true when VM does not exist' do + expect(subject.vm_exists?(pool_name, 'doesnotexist')).to eq(false) + end + end +end diff --git a/vmpooler.yaml.example b/vmpooler.yaml.example index c128b5d..2aab60c 100644 --- a/vmpooler.yaml.example +++ b/vmpooler.yaml.example @@ -25,6 +25,98 @@ username: 'vmpooler' password: 'swimsw1msw!m' +:providers: +# :providers: +# +# This section contains the VM providers for VMs and Pools +# The currently supported backing services are: +# - dummy + +# :dummy: +# +# The dummy backing service is a simple text file service that can be used +# to test vmpooler operations in a development or test environment +# +# Available configuration parameters: +# +# - filename (Optional) +# 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 +# +# - migratevm_couldmove_percent +# Percent chance that a VM could be moved to another host +# (optional; default 0%) +# +# - migratevm_max_time +# Maximum amount of random time a VM migration action will take in seconds +# (optional; default 0 seconds) +# +# - migratevm_fail_percent +# Percent chance that a VM migration action will fail +# (optional; default 0%) +# +# - getvm_poweroff_percent +# Percent chance that when the VM information is gathered that the VM will be powered off +# (optional; default 0%) +# +# - getvm_rename_percent +# Percent chance that when the VM information is gathered that the VM will be renamed +# (optional; default 0%) +# +# - createvm_max_time +# Maximum amount of random time a VM creation action will take, in seconds +# (optional; default 0 seconds) +# +# - createvm_fail_percent +# Percent chance that a VM creation action will fail +# (optional; default 0%) +# +# - createdisk_max_time +# Maximum amount of random time a VM create disk action will take, in seconds +# (optional; default 0 seconds) +# +# - createdisk_fail_percent +# Percent chance that a VM create disk action will fail +# (optional; default 0%) +# +# - createsnapshot_max_time +# Maximum amount of random time a VM create snapshot action will take, in seconds +# (optional; default 0 seconds) +# +# - createsnapshot_fail_percent +# Percent chance that a VM create snapshot action will fail +# (optional; default 0%) +# +# - revertsnapshot_max_time +# Maximum amount of random time a VM revert snapshot action will take, in seconds +# (optional; default 0 seconds) +# +# - revertsnapshot_fail_percent +# Percent chance that a VM revert snapshot action will fail +# (optional; default 0%) +# +# - destroyvm_max_shutdown_time +# Maximum amount of random time a VM shutdown action will take during destroy, in seconds +# (optional; default 0 seconds) +# +# - destroyvm_max_time +# Maximum amount of random time a VM destroy action will take, in seconds +# (optional; default 0 seconds) +# +# - destroyvm_fail_percent +# Percent chance that a VM destroy action will fail +# (optional; default 0%) +# +# - vmready_fail_percent +# Percent chance that an error is raised when vm_ready? is called +# (optional; default 0%) + +# Example: + + :dummy: + filename: '/tmp/dummy-backing.yaml' + + # :redis: # # This section contains the server hostname and authentication credentials