From d0c10104d17b660f25082faa4a7f2cc048ccd9a0 Mon Sep 17 00:00:00 2001 From: Glenn Sarti Date: Thu, 26 Jan 2017 15:26:21 -0800 Subject: [PATCH] (POOLER-72) Add Dummy VM Backing Service Previosuly, It is difficult to do development on VM Pooler as it requires a VSphere environment for VM creation etc.. This commit implements a Dummy backing service which behaves like VSphere but will just keep a VM registry in memory. This backing service can also inject failure into operations for testing how VM pooler behaves. --- lib/vmpooler/backingservice/dummy.rb | 200 +++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 lib/vmpooler/backingservice/dummy.rb diff --git a/lib/vmpooler/backingservice/dummy.rb b/lib/vmpooler/backingservice/dummy.rb new file mode 100644 index 0000000..0355f96 --- /dev/null +++ b/lib/vmpooler/backingservice/dummy.rb @@ -0,0 +1,200 @@ +require 'yaml' + +module Vmpooler + class PoolManager + class BackingService + class Dummy < Vmpooler::PoolManager::BackingService::Base + + # Fake VM backing service for testing, with initial configuration set in a simple text YAML filename + def initialize(options) + dummyfilename = options['filename'] + + # TODO Accessing @dummylist is not thread safe :-( Mutexes? + @dummylist = {} + + if !dummyfilename.nil? && File.exists?(dummyfilename) + @dummylist ||= YAML.load_file(dummyfilename) + end + end + + def vms_in_pool(pool) + get_pool_object(pool['name']).each do |vm| + vm + end + end + + def get_vm(vm) + dummy = get_dummy_vm(vm) + return nil if dummy.nil? + + obj = {} + # TODO Randomly power off the vm? + # TODO Randomly change the hostname of the vm? + obj['hostname'] = dummy['name'] + obj['boottime'] = dummy['boottime'] + obj['template'] = dummy['template'] + obj['poolname'] = dummy['poolname'] + obj['powerstate'] = dummy['powerstate'] + + obj + end + + def vm_exists?(vm) + !get_vm(vm).nil? + end + + def find_least_used_compatible_host(vm_name) + current_vm = get_dummy_vm(vm_name) + + # TODO parameterise this (75% chance it will not migrate) + return current_vm['vm_host'] if 1 + rand(100) < 75 + + # TODO paramtise this (Simulates a 10 node cluster) + (1 + rand(10)).to_s + end + + def get_vm_host(vm_name) + current_vm = get_dummy_vm(vm_name) + + current_vm['vm_host'] + end + + def migrate_vm_to_host(vm_name, dest_host_name) + current_vm = get_dummy_vm(vm_name) + + # TODO do I need fake a random sleep for ready? + # TODO Should I inject a random error? + + sleep(1) + current_vm['vm_host'] = dest_host_name + + true + end + + def is_vm_ready?(vm,pool,timeout) + host = get_dummy_vm(vm) + if !host then return false end + if host['poolname'] != pool then return false end + if vm['ready'] then return true end + # TODO do I need fake a random sleep for ready? + # TODO Should I inject a random error? + sleep(2) + host['ready'] = true + + true + end + + def create_vm(pool) + # This is an async operation + # This code just clones a VM and starts it + # Later checking will move it from the pending to ready queue + Thread.new do + begin + template_name = pool['template'] + pool_name = pool['name'] + + # Generate a randomized hostname + o = [('a'..'z'), ('0'..'9')].map(&:to_a).flatten + dummy_hostname = $config[:config]['prefix'] + o[rand(25)] + (0...14).map { o[rand(o.length)] }.join + + vm = {} + vm['name'] = dummy_hostname + vm['hostname'] = dummy_hostname + vm['domain'] = 'dummy.local' + vm['vm_template'] = template_name + # 'template' is the Template in API, not the template to create the VM ('vm_template') + vm['template'] = pool_name + vm['poolname'] = pool_name + vm['ready'] = false + vm['boottime'] = Time.now + vm['powerstate'] = 'PoweredOn' + vm['vm_host'] = '1' + get_pool_object(pool_name) + @dummylist['pool'][pool_name] << vm + + # Add VM to Redis inventory ('pending' pool) + $redis.sadd('vmpooler__pending__' + pool_name, vm['hostname']) + $redis.hset('vmpooler__vm__' + vm['hostname'], 'clone', Time.now) + $redis.hset('vmpooler__vm__' + vm['hostname'], 'template', vm['template']) + + $logger.log('d', "[ ] [#{pool_name}] '#{dummy_hostname}' is being cloned from '#{template_name}'") + begin + start = Time.now + + # TODO do I need fake a random sleep to clone + sleep(2) + + # TODO Inject random clone failure + finish = '%.2f' % (Time.now - start) + + $redis.hset('vmpooler__clone__' + Date.today.to_s, vm['template'] + ':' + vm['hostname'], finish) + $redis.hset('vmpooler__vm__' + vm['hostname'], 'clone_time', finish) + + $logger.log('s', "[+] [#{vm['template']}] '#{vm['hostname']}' cloned from '#{vm['template']}' in #{finish} seconds") + rescue => err + $logger.log('s', "[!] [#{vm['template']}] '#{vm['hostname']}' clone failed with an error: #{err}") + $redis.srem('vmpooler__pending__' + vm['template'], vm['hostname']) + raise + end + + $redis.decr('vmpooler__tasks__clone') + + $metrics.timing("clone.#{vm['template']}", finish) + dummy_hostname + rescue => err + $logger.log('s', "[!] [#{vm['template']}] '#{vm['hostname']}' failed while preparing to clone with an error: #{err}") + raise + end + end + end + + def destroy_vm(vm_name,pool) + vm = get_dummy_vm(vm_name) + if !vm then return false end + if vm['poolname'] != pool then return false end + + start = Time.now + + # Shutdown down the VM if it's poweredOn + if vm['powerstate'] = 'PoweredOn' + $logger.log('d', "[ ] [#{pool}] '#{vm_name}' is being shut down") + # TODO Use random shutdown interval + sleep(2) + vm['powerstate'] = 'PoweredOff' + end + + # 'Destroy' the VM + new_poollist = @dummylist['pool'][pool].delete_if { |vm| vm['name'] == vm_name } + @dummylist['pool'][pool] = new_poollist + + # TODO Use random destruction interval + sleep(2) + + finish = '%.2f' % (Time.now - start) + + $logger.log('s', "[-] [#{pool}] '#{vm_name}' destroyed in #{finish} seconds") + $metrics.timing("destroy.#{pool}", finish) + end + + private + # Get's the pool config safely from the in-memory hashtable + def get_pool_object(pool_name) + @dummylist['pool'] = {} if @dummylist['pool'].nil? + @dummylist['pool'][pool_name] = [] if @dummylist['pool'][pool_name].nil? + + return @dummylist['pool'][pool_name] + end + + def get_dummy_vm(vm) + @dummylist['pool'].keys.each do |poolname| + @dummylist['pool'][poolname].each do |poolvm| + return poolvm if poolvm['name'] == vm + end + end + + nil + end + end + end + end +end \ No newline at end of file