(POOLER-129) Allow setting weights for backends

This commit updates get_vm in the vmpooler API to allow for setting weights for backends. Additionally, when an alias for a pool exists, and the backend configured is not weighted, then the selection of the pool based on alias will be randomly sampled. Without this change any pool with the title of the alias is exhausted before an alternate pool with the configured alias is used, which results in an uneven distribution of VMs. When all backends involved are configured with weighted values the VM selection will be based on probability using those weights.

A bug is fixed when setting the default ttl for check_ready_vm.

Pickup is added to handle weighted VM selection.

A dockerfile is added that allows for building and installing vmpooler
from the current HEAD in docker to make for easy testing.
This commit is contained in:
kirby@puppetlabs.com 2018-09-11 11:14:51 -07:00
parent 0e86937245
commit cd73f53561
6 changed files with 69 additions and 26 deletions

View file

@ -1,6 +1,7 @@
source ENV['GEM_SOURCE'] || 'https://rubygems.org' source ENV['GEM_SOURCE'] || 'https://rubygems.org'
gem 'json', '>= 1.8' gem 'json', '>= 1.8'
gem 'pickup', '~> 0.0.11'
gem 'puma', '~> 3.11' gem 'puma', '~> 3.11'
gem 'rack', '~> 2.0' gem 'rack', '~> 2.0'
gem 'rake', '~> 12.3' gem 'rake', '~> 12.3'

View file

@ -1,15 +1,16 @@
module Vmpooler module Vmpooler
require 'date' require 'date'
require 'json' require 'json'
require 'open-uri'
require 'net/ldap' require 'net/ldap'
require 'open-uri'
require 'pickup'
require 'rbvmomi' require 'rbvmomi'
require 'redis' require 'redis'
require 'set'
require 'sinatra/base' require 'sinatra/base'
require 'time' require 'time'
require 'timeout' require 'timeout'
require 'yaml' require 'yaml'
require 'set'
%w[api graphite logger pool_manager statsd dummy_statsd generic_connection_pool].each do |lib| %w[api graphite logger pool_manager statsd dummy_statsd generic_connection_pool].each do |lib|
require "vmpooler/#{lib}" require "vmpooler/#{lib}"

View file

@ -36,16 +36,46 @@ module Vmpooler
validate_token(backend) validate_token(backend)
end end
def fetch_single_vm(template) def pool_backend(pool)
vm = backend.spop('vmpooler__ready__' + template) pool_index = pool_index(pools)
return [vm, template] if vm pool_config = pools[pool_index[pool]]
backend = pool_config['clone_target'] || config['clone_target'] || 'default'
aliases = Vmpooler::API.settings.config[:alias] return backend
if aliases && aliased_template = aliases[template]
vm = backend.spop('vmpooler__ready__' + aliased_template)
return [vm, aliased_template] if vm
end end
def fetch_single_vm(template)
template_backends = [template]
aliases = Vmpooler::API.settings.config[:alias]
if aliases
template_backends << aliases[template] if aliases[template]
pool_index = pool_index(pools)
weighted_pools = {}
template_backends.each do |t|
next unless pool_index.key? t
index = pool_index[t]
clone_target = pools[index]['clone_target'] || config['clone_target']
next unless config.key?('backend_weight')
weight = config['backend_weight'][clone_target]
if weight
weighted_pools[t] = weight
end
end
if weighted_pools.count == template_backends.count
pickup = Pickup.new(weighted_pools)
selection = pickup.pick
template_backends.delete(selection)
template_backends.unshift(selection)
else
template_backends = template_backends.sample(template_backends.count)
end
end
template_backends.each do |t|
vm = backend.spop('vmpooler__ready__' + t)
return [vm, t] if vm
end
[nil, nil] [nil, nil]
end end

View file

@ -149,12 +149,15 @@ module Vmpooler
# Periodically check that the VM is available # Periodically check that the VM is available
mutex = vm_mutex(vm) mutex = vm_mutex(vm)
return if mutex.locked? return if mutex.locked?
begin
mutex.synchronize do mutex.synchronize do
stage = 'stamp check'
check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check') check_stamp = $redis.hget('vmpooler__vm__' + vm, 'check')
return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime']) return if check_stamp && (((Time.now - Time.parse(check_stamp)) / 60) <= $config[:config]['vm_checktime'])
$redis.hset('vmpooler__vm__' + vm, 'check', Time.now) $redis.hset('vmpooler__vm__' + vm, 'check', Time.now)
# Check if the hosts TTL has expired # Check if the hosts TTL has expired
stage = 'ttl'
if ttl > 0 if ttl > 0
# host['boottime'] may be nil if host is not powered on # host['boottime'] may be nil if host is not powered on
if ((Time.now - host['boottime']) / 60).to_s[/^\d+\.\d{1}/].to_f > ttl if ((Time.now - host['boottime']) / 60).to_s[/^\d+\.\d{1}/].to_f > ttl
@ -165,10 +168,16 @@ module Vmpooler
end end
end end
stage = 'hostname mismatch'
return if has_mismatched_hostname?(vm, pool, provider) return if has_mismatched_hostname?(vm, pool, provider)
stage = 'still ready'
vm_still_ready?(pool['name'], vm, provider) vm_still_ready?(pool['name'], vm, provider)
end end
rescue => err
$logger.log('s', "Failed at stage #{stage} for #{vm}")
raise
end
end end
def has_mismatched_hostname?(vm, pool, provider) def has_mismatched_hostname?(vm, pool, provider)
@ -190,6 +199,7 @@ module Vmpooler
vm_hash = provider.get_vm(pool['name'], vm) vm_hash = provider.get_vm(pool['name'], vm)
hostname = vm_hash['hostname'] hostname = vm_hash['hostname']
return if hostname.nil?
return if hostname.empty? return if hostname.empty?
return if hostname == vm return if hostname == vm
$redis.smove('vmpooler__ready__' + pool['name'], 'vmpooler__completed__' + pool['name'], vm) $redis.smove('vmpooler__ready__' + pool['name'], 'vmpooler__completed__' + pool['name'], vm)
@ -865,12 +875,12 @@ module Vmpooler
end end
end end
def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl = 0) def check_ready_pool_vms(pool_name, provider, pool_check_response, inventory, pool_ttl)
$redis.smembers("vmpooler__ready__#{pool_name}").each do |vm| $redis.smembers("vmpooler__ready__#{pool_name}").each do |vm|
if inventory[vm] if inventory[vm]
begin begin
pool_check_response[:checked_ready_vms] += 1 pool_check_response[:checked_ready_vms] += 1
check_ready_vm(vm, pool_name, pool_ttl, provider) check_ready_vm(vm, pool_name, pool_ttl || 0, provider)
rescue => err rescue => err
$logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{err}") $logger.log('d', "[!] [#{pool_name}] _check_pool failed with an error while evaluating ready VMs: #{err}")
end end

View file

@ -428,7 +428,7 @@ module Vmpooler
def vm_ready?(_pool_name, vm_name) def vm_ready?(_pool_name, vm_name)
begin begin
open_socket(vm_name) open_socket(vm_name, global_config[:config]['domain'])
rescue => _err rescue => _err
return false return false
end end

View file

@ -17,6 +17,7 @@ Gem::Specification.new do |s|
s.bindir = 'bin' s.bindir = 'bin'
s.executables = 'vmpooler' s.executables = 'vmpooler'
s.require_paths = ["lib"] s.require_paths = ["lib"]
s.add_dependency 'pickup', '~> 0.0.11'
s.add_dependency 'puma', '~> 3.11' s.add_dependency 'puma', '~> 3.11'
s.add_dependency 'rack', '~> 2.0' s.add_dependency 'rack', '~> 2.0'
s.add_dependency 'rake', '~> 12.3' s.add_dependency 'rake', '~> 12.3'