vmpooler/spec/unit/providers/dummy_spec.rb
kirby@puppetlabs.com 86e92de4cf (POOLER-158) Add capability to provision VMs on demand
This change adds a capability to vmpooler to provision instances on
demand. Without this change vmpooler only supports retrieving machines
from pre-provisioned pools.

Additionally, this change refactors redis interactions to reduce round
trips to redis. Specifically, multi and pipelined redis commands are
added where possible to reduce the number of times we are calling redis.

To support the redis refactor the redis interaction has changed to
leveraging a connection pool. In addition to offering multiple
connections for pool manager to use, the redis interactions in pool
manager are now thread safe.

Ready TTL is now a global parameter that can be set as a default for all
pools. A default of 0 has been removed, because this is an unreasonable
default behavior, which would leave a provisioned instance in the pool
indefinitely.

Pool empty messages have been removed when the pool size is set to 0.
Without this change, when a pool was set to a size of 0 the API and pool
manager would both show that a pool is empty.
2020-05-15 14:12:36 -07:00

512 lines
15 KiB
Ruby

require 'spec_helper'
require 'vmpooler/providers/dummy'
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:
: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
- name: 'pool2'
size: 5
EOT
)
}
let(:redis_connection_pool) { ConnectionPool.new(size: 1) { MockRedis.new } }
subject { Vmpooler::PoolManager::Provider::Dummy.new(config, logger, metrics, redis_connection_pool, '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