mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 10:08:40 -05:00
Merge pull request #332 from puppetlabs/pooler-143
(POOLER-143) Add clone_target config change to API
This commit is contained in:
commit
8a3f0bb742
4 changed files with 222 additions and 3 deletions
|
|
@ -194,6 +194,26 @@ module Vmpooler
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_clone_target(payload)
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
pools_updated = 0
|
||||||
|
sync_clone_targets
|
||||||
|
|
||||||
|
payload.each do |poolname, clone_target|
|
||||||
|
unless pools[pool_index[poolname]]['clone_target'] == clone_target
|
||||||
|
pools[pool_index[poolname]]['clone_target'] == clone_target
|
||||||
|
backend.hset('vmpooler__config__clone_target', poolname, clone_target)
|
||||||
|
pools_updated += 1
|
||||||
|
status 201
|
||||||
|
end
|
||||||
|
end
|
||||||
|
status 200 unless pools_updated > 0
|
||||||
|
result['ok'] = true
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
def sync_pool_templates
|
def sync_pool_templates
|
||||||
pool_index = pool_index(pools)
|
pool_index = pool_index(pools)
|
||||||
template_configs = backend.hgetall('vmpooler__config__template')
|
template_configs = backend.hgetall('vmpooler__config__template')
|
||||||
|
|
@ -222,6 +242,20 @@ module Vmpooler
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sync_clone_targets
|
||||||
|
pool_index = pool_index(pools)
|
||||||
|
clone_target_configs = backend.hgetall('vmpooler__config__clone_target')
|
||||||
|
unless clone_target_configs.nil?
|
||||||
|
clone_target_configs.each do |poolname, clone_target|
|
||||||
|
if pool_index.include? poolname
|
||||||
|
unless pools[pool_index[poolname]]['clone_target'] == clone_target
|
||||||
|
pools[pool_index[poolname]]['clone_target'] == clone_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
get '/' do
|
get '/' do
|
||||||
sync_pool_sizes
|
sync_pool_sizes
|
||||||
redirect to('/dashboard/')
|
redirect to('/dashboard/')
|
||||||
|
|
@ -700,6 +734,14 @@ module Vmpooler
|
||||||
invalid
|
invalid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def invalid_pool(payload)
|
||||||
|
invalid = []
|
||||||
|
payload.each do |pool, clone_target|
|
||||||
|
invalid << pool unless pool_exists?(pool)
|
||||||
|
end
|
||||||
|
invalid
|
||||||
|
end
|
||||||
|
|
||||||
post "#{api_prefix}/vm/:template/?" do
|
post "#{api_prefix}/vm/:template/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
result = { 'ok' => false }
|
result = { 'ok' => false }
|
||||||
|
|
@ -1011,6 +1053,37 @@ module Vmpooler
|
||||||
JSON.pretty_generate(result)
|
JSON.pretty_generate(result)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post "#{api_prefix}/config/clonetarget/?" do
|
||||||
|
content_type :json
|
||||||
|
result = { 'ok' => false }
|
||||||
|
|
||||||
|
if config['experimental_features']
|
||||||
|
need_token! if Vmpooler::API.settings.config[:auth]
|
||||||
|
|
||||||
|
payload = JSON.parse(request.body.read)
|
||||||
|
|
||||||
|
if payload
|
||||||
|
invalid = invalid_pool(payload)
|
||||||
|
if invalid.empty?
|
||||||
|
result = update_clone_target(payload)
|
||||||
|
else
|
||||||
|
invalid.each do |bad_template|
|
||||||
|
metrics.increment("config.invalid.#{bad_template}")
|
||||||
|
end
|
||||||
|
result[:bad_templates] = invalid
|
||||||
|
status 400
|
||||||
|
end
|
||||||
|
else
|
||||||
|
metrics.increment('config.invalid.unknown')
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
else
|
||||||
|
status 405
|
||||||
|
end
|
||||||
|
|
||||||
|
JSON.pretty_generate(result)
|
||||||
|
end
|
||||||
|
|
||||||
get "#{api_prefix}/config/?" do
|
get "#{api_prefix}/config/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
result = { 'ok' => false }
|
result = { 'ok' => false }
|
||||||
|
|
|
||||||
|
|
@ -680,6 +680,10 @@ module Vmpooler
|
||||||
initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
initial_ready_size = $redis.scard("vmpooler__ready__#{options[:poolname]}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:clone_target_change]
|
||||||
|
initial_clone_target = $redis.hget("vmpooler__pool__#{options[:poolname]}", options[:clone_target])
|
||||||
|
end
|
||||||
|
|
||||||
if options[:pool_template_change]
|
if options[:pool_template_change]
|
||||||
initial_template = $redis.hget('vmpooler__template__prepared', options[:poolname])
|
initial_template = $redis.hget('vmpooler__template__prepared', options[:poolname])
|
||||||
end
|
end
|
||||||
|
|
@ -698,6 +702,13 @@ module Vmpooler
|
||||||
break unless ready_size == initial_ready_size
|
break unless ready_size == initial_ready_size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if options[:clone_target_change]
|
||||||
|
clone_target = $redis.hget("vmpooler__config__clone_target}", options[:poolname])
|
||||||
|
if clone_target
|
||||||
|
break unless clone_target == initial_clone_target
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if options[:pool_template_change]
|
if options[:pool_template_change]
|
||||||
configured_template = $redis.hget('vmpooler__config__template', options[:poolname])
|
configured_template = $redis.hget('vmpooler__config__template', options[:poolname])
|
||||||
if configured_template
|
if configured_template
|
||||||
|
|
@ -742,7 +753,7 @@ module Vmpooler
|
||||||
loop_delay = (loop_delay * loop_delay_decay).to_i
|
loop_delay = (loop_delay * loop_delay_decay).to_i
|
||||||
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
loop_delay = loop_delay_max if loop_delay > loop_delay_max
|
||||||
end
|
end
|
||||||
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true)
|
sleep_with_wakeup_events(loop_delay, loop_delay_min, pool_size_change: true, poolname: pool['name'], pool_template_change: true, clone_target_change: true)
|
||||||
|
|
||||||
unless maxloop.zero?
|
unless maxloop.zero?
|
||||||
break if loop_count >= maxloop
|
break if loop_count >= maxloop
|
||||||
|
|
@ -847,6 +858,21 @@ module Vmpooler
|
||||||
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_clone_target(pool)
|
||||||
|
mutex = pool_mutex(pool['name'])
|
||||||
|
return if mutex.locked?
|
||||||
|
clone_target = $redis.hget('vmpooler__config__clone_target', pool['name'])
|
||||||
|
return if clone_target.nil?
|
||||||
|
return if clone_target == pool['clone_target']
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] clone updated from #{pool['clone_target']} to #{clone_target}")
|
||||||
|
mutex.synchronize do
|
||||||
|
pool['clone_target'] = clone_target
|
||||||
|
# Remove all ready and pending VMs so new instances are created for the new clone_target
|
||||||
|
drain_pool(pool['name'])
|
||||||
|
end
|
||||||
|
$logger.log('s', "[*] [#{pool['name']}] is ready for use")
|
||||||
|
end
|
||||||
|
|
||||||
def remove_excess_vms(pool)
|
def remove_excess_vms(pool)
|
||||||
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
ready = $redis.scard("vmpooler__ready__#{pool['name']}")
|
||||||
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
total = $redis.scard("vmpooler__pending__#{pool['name']}") + ready
|
||||||
|
|
@ -1080,6 +1106,10 @@ module Vmpooler
|
||||||
# otherwise identify this change when running
|
# otherwise identify this change when running
|
||||||
update_pool_size(pool)
|
update_pool_size(pool)
|
||||||
|
|
||||||
|
# Check to see if a pool size change has been made via the configuration API
|
||||||
|
# Additionally, a pool will drain ready and pending instances
|
||||||
|
update_clone_target(pool)
|
||||||
|
|
||||||
repopulate_pool_vms(pool['name'], provider, pool_check_response, pool['size'])
|
repopulate_pool_vms(pool['name'], provider, pool_check_response, pool['size'])
|
||||||
|
|
||||||
# Remove VMs in excess of the configured pool size
|
# Remove VMs in excess of the configured pool size
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ describe Vmpooler::API::V1 do
|
||||||
'experimental_features' => true
|
'experimental_features' => true
|
||||||
},
|
},
|
||||||
pools: [
|
pools: [
|
||||||
{'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1'},
|
{'name' => 'pool1', 'size' => 5, 'template' => 'templates/pool1', 'clone_target' => 'default_cluster'},
|
||||||
{'name' => 'pool2', 'size' => 10}
|
{'name' => 'pool2', 'size' => 10}
|
||||||
],
|
],
|
||||||
statsd: { 'prefix' => 'stats_prefix'},
|
statsd: { 'prefix' => 'stats_prefix'},
|
||||||
|
|
@ -223,6 +223,69 @@ describe Vmpooler::API::V1 do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST /config/clonetarget' do
|
||||||
|
it 'changes the clone target' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool1":"cluster1"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'changes a pool size for multiple pools' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool1":"cluster1","pool2":"cluster2"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails when a specified pool does not exist' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool10":"cluster1"}'
|
||||||
|
expect_json(ok = false, http = 400)
|
||||||
|
expected = {
|
||||||
|
ok: false,
|
||||||
|
bad_templates: ['pool10']
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with 200 when no change is required' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool1":"default_cluster"}'
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'succeeds with 201 when at least one pool changes' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool1":"default_cluster","pool2":"cluster2"}'
|
||||||
|
expect_json(ok = true, http = 201)
|
||||||
|
|
||||||
|
expected = { ok: true }
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with experimental features disabled' do
|
||||||
|
before(:each) do
|
||||||
|
config[:config]['experimental_features'] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return 405' do
|
||||||
|
post "#{prefix}/config/clonetarget", '{"pool1":"cluster1"}'
|
||||||
|
expect_json(ok = false, http = 405)
|
||||||
|
|
||||||
|
expected = { ok: false }
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET /config' do
|
describe 'GET /config' do
|
||||||
let(:prefix) { '/api/v1' }
|
let(:prefix) { '/api/v1' }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2390,6 +2390,59 @@ EOT
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'update_clone_target' do
|
||||||
|
let(:newtarget) { 'cluster2' }
|
||||||
|
let(:config) {
|
||||||
|
YAML.load(<<-EOT
|
||||||
|
---
|
||||||
|
:pools:
|
||||||
|
- name: #{pool}
|
||||||
|
clone_target: 'cluster1'
|
||||||
|
EOT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let(:poolconfig) { config[:pools][0] }
|
||||||
|
|
||||||
|
context 'with a locked mutex' do
|
||||||
|
|
||||||
|
let(:mutex) { Mutex.new }
|
||||||
|
before(:each) do
|
||||||
|
mutex.lock
|
||||||
|
expect(subject).to receive(:pool_mutex).with(pool).and_return(mutex)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return nil' do
|
||||||
|
expect(subject.update_clone_target(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should get the pool clone target configuration from redis' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool)
|
||||||
|
|
||||||
|
subject.update_clone_target(poolconfig)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return when clone_target is not set in redis' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(nil)
|
||||||
|
|
||||||
|
expect(subject.update_clone_target(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should return when no change in configuration is required' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return('cluster1')
|
||||||
|
|
||||||
|
expect(subject.update_clone_target(poolconfig)).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should update the clone target' do
|
||||||
|
expect(redis).to receive(:hget).with('vmpooler__config__clone_target', pool).and_return(newtarget)
|
||||||
|
|
||||||
|
subject.update_clone_target(poolconfig)
|
||||||
|
|
||||||
|
expect(poolconfig['clone_target']).to eq(newtarget)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "#execute!" do
|
describe "#execute!" do
|
||||||
let(:config) {
|
let(:config) {
|
||||||
YAML.load(<<-EOT
|
YAML.load(<<-EOT
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue