class Vmpooler::PoolManager::Provider::Dummy

Public Class Methods

new(config, logger, metrics, redis_connection_pool, name, options) click to toggle source

Fake VM Provider for testing

# File lib/vmpooler/providers/dummy.rb, line 12
def initialize(config, logger, metrics, redis_connection_pool, name, options)
  super(config, logger, metrics, redis_connection_pool, 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

  # 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,
    connpool_type: 'provider_connection_pool',
    connpool_provider: name,
    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

Public Instance Methods

create_disk(pool_name, vm_name, disk_size) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 212
def create_disk(pool_name, vm_name, disk_size)
  @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['createdisk_max_time'].nil?
      delay = 1 + rand(provider_config['createdisk_max_time'])
      sleep(delay)
    end

    # Inject create failure
    raise('Dummy Failure for createdisk_fail_percent') if !provider_config['createdisk_fail_percent'].nil? && rand(1..100) <= provider_config['createdisk_fail_percent']

    @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
create_snapshot(pool_name, vm_name, snapshot_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 236
def create_snapshot(pool_name, vm_name, snapshot_name)
  @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['createsnapshot_max_time'].nil?
      delay = 1 + rand(provider_config['createsnapshot_max_time'])
      sleep(delay)
    end

    # Inject create failure
    raise('Dummy Failure for createsnapshot_fail_percent') if !provider_config['createsnapshot_fail_percent'].nil? && rand(1..100) <= provider_config['createsnapshot_fail_percent']

    @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
create_vm(pool_name, dummy_hostname) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 148
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}'")

  @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

    begin
      # Inject clone failure
      raise('Dummy Failure for createvm_fail_percent') if !provider_config['createvm_fail_percent'].nil? && rand(1..100) <= provider_config['createvm_fail_percent']

      # Assert the VM is ready for use
      @write_lock.synchronize do
        vm['dummy_state'] = 'RUNNING'
        write_backing_file
      end
    rescue StandardError => _e
      @write_lock.synchronize do
        remove_dummy_vm(pool_name, dummy_hostname)
        write_backing_file
      end
      raise
    end
  end

  get_vm(pool_name, dummy_hostname)
end
destroy_vm(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 279
def destroy_vm(pool_name, vm_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")

      # 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
    raise('Dummy Failure for migratevm_fail_percent') if !provider_config['destroyvm_fail_percent'].nil? && rand(1..100) <= provider_config['destroyvm_fail_percent']

    # 'Destroy' the VM
    @write_lock.synchronize do
      remove_dummy_vm(pool_name, vm_name)
      write_backing_file
    end
  end

  true
end
find_least_used_compatible_host(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 69
def find_least_used_compatible_host(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?

  # Only migrate if migratevm_couldmove_percent is met
  return current_vm['vm_host'] if rand(1..100) > provider_config['migratevm_couldmove_percent']

  # Simulate a 10 node cluster and randomly pick a different one
  new_host = "HOST#{rand(1..10)}" while new_host == current_vm['vm_host']

  new_host
end
get_vm(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 110
def get_vm(pool_name, vm_name)
  obj = {}
  @connection_pool.with_metrics do |_conn|
    dummy = get_dummy_vm(pool_name, vm_name)
    return nil if dummy.nil?

    # Randomly power off the VM
    if !(dummy['powerstate'] != 'PoweredOn' || provider_config['getvm_poweroff_percent'].nil?) && rand(1..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

    # Randomly rename the host
    if !(dummy['hostname'] != dummy['name'] || provider_config['getvm_rename_percent'].nil?) && rand(1..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

    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
get_vm_host(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 60
def get_vm_host(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
migrate_vm_to_host(pool_name, vm_name, dest_host_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 87
def migrate_vm_to_host(pool_name, vm_name, dest_host_name)
  @connection_pool.with_metrics do |_conn|
    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
    raise('Dummy Failure for migratevm_fail_percent') if !provider_config['migratevm_fail_percent'].nil? && rand(1..100) <= provider_config['migratevm_fail_percent']

    @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
name() click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 44
def name
  'dummy'
end
revert_snapshot(pool_name, vm_name, snapshot_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 260
def revert_snapshot(pool_name, vm_name, snapshot_name)
  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 failure
    raise('Dummy Failure for revertsnapshot_fail_percent') if !provider_config['revertsnapshot_fail_percent'].nil? && rand(1..100) <= provider_config['revertsnapshot_fail_percent']
  end

  vm_object['snapshots'].include?(snapshot_name)
end
vm_ready?(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 321
def vm_ready?(pool_name, vm_name)
  @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.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)

    raise('Dummy Failure for vmready_fail_percent') if !provider_config['vmready_fail_percent'].nil? && rand(1..100) <= provider_config['vmready_fail_percent']

    @write_lock.synchronize do
      vm_object['ready'] = true
      write_backing_file
    end
  end

  true
end
vms_in_pool(pool_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 48
def vms_in_pool(pool_name)
  vmlist = []

  @connection_pool.with_metrics do |_conn|
    get_dummy_pool_object(pool_name).each do |vm|
      vmlist << { 'name' => vm['name'] }
    end
  end

  vmlist
end

Private Instance Methods

get_dummy_pool_object(pool_name) click to toggle source

Get's the pool config safely from the in-memory hashtable

# File lib/vmpooler/providers/dummy.rb, line 364
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
get_dummy_vm(pool_name, vm_name) click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 371
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
remove_dummy_vm(pool_name, vm_name) click to toggle source

Note - NEVER EVER use the @write_lock object in the private methods!!!! Deadlocks will ensue

# File lib/vmpooler/providers/dummy.rb, line 356
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
write_backing_file() click to toggle source
# File lib/vmpooler/providers/dummy.rb, line 381
def write_backing_file
  dummyfilename = provider_config['filename']
  return if dummyfilename.nil?

  File.open(dummyfilename, 'w') { |file| file.write(YAML.dump(@dummylist)) }
end