mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 01:58:41 -05:00
Merge pull request #138 from sschneid/qeng_2807
(QENG-2807) Allow pool 'alias' names
This commit is contained in:
commit
20fa7d20be
4 changed files with 274 additions and 82 deletions
|
|
@ -33,6 +33,20 @@ module Vmpooler
|
||||||
parsed_config[:config]['vm_checktime'] ||= 15
|
parsed_config[:config]['vm_checktime'] ||= 15
|
||||||
parsed_config[:config]['vm_lifetime'] ||= 24
|
parsed_config[:config]['vm_lifetime'] ||= 24
|
||||||
|
|
||||||
|
# Create an index of pool aliases
|
||||||
|
parsed_config[:pools].each do |pool|
|
||||||
|
if pool['alias']
|
||||||
|
if pool['alias'].kind_of?(Array)
|
||||||
|
pool['alias'].each do |a|
|
||||||
|
parsed_config[:alias] ||= {}
|
||||||
|
parsed_config[:alias][a] = pool['name']
|
||||||
|
end
|
||||||
|
elsif pool['alias'].kind_of?(String)
|
||||||
|
parsed_config[:alias][pool['alias']] = pool['name']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if parsed_config[:graphite]['server']
|
if parsed_config[:graphite]['server']
|
||||||
parsed_config[:graphite]['prefix'] ||= 'vmpooler'
|
parsed_config[:graphite]['prefix'] ||= 'vmpooler'
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,60 @@ module Vmpooler
|
||||||
validate_token(backend)
|
validate_token(backend)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def alias_deref(hash)
|
||||||
|
newhash = {}
|
||||||
|
|
||||||
|
hash.each do |key, val|
|
||||||
|
if backend.exists('vmpooler__ready__' + key)
|
||||||
|
newhash[key] = val
|
||||||
|
else
|
||||||
|
if Vmpooler::API.settings.config[:alias][key]
|
||||||
|
newkey = Vmpooler::API.settings.config[:alias][key]
|
||||||
|
newhash[newkey] = val
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
newhash
|
||||||
|
end
|
||||||
|
|
||||||
|
def checkout_vm(template, result)
|
||||||
|
vm = backend.spop('vmpooler__ready__' + template)
|
||||||
|
|
||||||
|
unless vm.nil?
|
||||||
|
backend.sadd('vmpooler__running__' + template, vm)
|
||||||
|
backend.hset('vmpooler__active__' + template, vm, Time.now)
|
||||||
|
backend.hset('vmpooler__vm__' + vm, 'checkout', Time.now)
|
||||||
|
|
||||||
|
if Vmpooler::API.settings.config[:auth] and has_token?
|
||||||
|
validate_token(backend)
|
||||||
|
|
||||||
|
backend.hset('vmpooler__vm__' + vm, 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
||||||
|
backend.hset('vmpooler__vm__' + vm, 'token:user',
|
||||||
|
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user')
|
||||||
|
)
|
||||||
|
|
||||||
|
if config['vm_lifetime_auth'].to_i > 0
|
||||||
|
backend.hset('vmpooler__vm__' + vm, 'lifetime', config['vm_lifetime_auth'].to_i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
result[template] ||= {}
|
||||||
|
|
||||||
|
if result[template]['hostname']
|
||||||
|
result[template]['hostname'] = [result[template]['hostname']] unless result[template]['hostname'].is_a?(Array)
|
||||||
|
result[template]['hostname'].push(vm)
|
||||||
|
else
|
||||||
|
result[template]['hostname'] = vm
|
||||||
|
end
|
||||||
|
else
|
||||||
|
status 503
|
||||||
|
result['ok'] = false
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
get "#{api_prefix}/status/?" do
|
get "#{api_prefix}/status/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
|
|
||||||
|
|
@ -282,11 +336,15 @@ module Vmpooler
|
||||||
post "#{api_prefix}/vm/?" do
|
post "#{api_prefix}/vm/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
|
|
||||||
result = {}
|
result = { 'ok' => false }
|
||||||
|
|
||||||
available = 1
|
jdata = alias_deref(JSON.parse(request.body.read))
|
||||||
|
|
||||||
jdata = JSON.parse(request.body.read)
|
if not jdata.nil? and not jdata.empty?
|
||||||
|
available = 1
|
||||||
|
else
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
|
||||||
jdata.each do |key, val|
|
jdata.each do |key, val|
|
||||||
if backend.scard('vmpooler__ready__' + key).to_i < val.to_i
|
if backend.scard('vmpooler__ready__' + key).to_i < val.to_i
|
||||||
|
|
@ -298,46 +356,10 @@ module Vmpooler
|
||||||
result['ok'] = true
|
result['ok'] = true
|
||||||
|
|
||||||
jdata.each do |key, val|
|
jdata.each do |key, val|
|
||||||
result[key] ||= {}
|
|
||||||
|
|
||||||
val.to_i.times do |_i|
|
val.to_i.times do |_i|
|
||||||
vm = backend.spop('vmpooler__ready__' + key)
|
result = checkout_vm(key, result)
|
||||||
|
|
||||||
unless vm.nil?
|
|
||||||
backend.sadd('vmpooler__running__' + key, vm)
|
|
||||||
backend.hset('vmpooler__active__' + key, vm, Time.now)
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'checkout', Time.now)
|
|
||||||
|
|
||||||
if Vmpooler::API.settings.config[:auth] and has_token?
|
|
||||||
validate_token(backend)
|
|
||||||
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'token:user',
|
|
||||||
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user')
|
|
||||||
)
|
|
||||||
|
|
||||||
if config['vm_lifetime_auth'].to_i > 0
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'lifetime', config['vm_lifetime_auth'].to_i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
result[key] ||= {}
|
|
||||||
|
|
||||||
if result[key]['hostname']
|
|
||||||
result[key]['hostname'] = [result[key]['hostname']] unless result[key]['hostname'].is_a?(Array)
|
|
||||||
result[key]['hostname'].push(vm)
|
|
||||||
else
|
|
||||||
result[key]['hostname'] = vm
|
|
||||||
end
|
|
||||||
else
|
|
||||||
status 503
|
|
||||||
result['ok'] = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
status 503
|
|
||||||
result['ok'] = false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if result['ok'] && config['domain']
|
if result['ok'] && config['domain']
|
||||||
|
|
@ -350,7 +372,7 @@ module Vmpooler
|
||||||
post "#{api_prefix}/vm/:template/?" do
|
post "#{api_prefix}/vm/:template/?" do
|
||||||
content_type :json
|
content_type :json
|
||||||
|
|
||||||
result = {}
|
result = { 'ok' => false }
|
||||||
payload = {}
|
payload = {}
|
||||||
|
|
||||||
params[:template].split('+').each do |template|
|
params[:template].split('+').each do |template|
|
||||||
|
|
@ -358,10 +380,16 @@ module Vmpooler
|
||||||
payload[template] = payload[template] + 1
|
payload[template] = payload[template] + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
available = 1
|
payload = alias_deref(payload)
|
||||||
|
|
||||||
payload.keys.each do |template|
|
if not payload.nil? and not payload.empty?
|
||||||
if backend.scard('vmpooler__ready__' + template) < payload[template]
|
available = 1
|
||||||
|
else
|
||||||
|
status 404
|
||||||
|
end
|
||||||
|
|
||||||
|
payload.each do |key, val|
|
||||||
|
if backend.scard('vmpooler__ready__' + key).to_i < val.to_i
|
||||||
available = 0
|
available = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -369,45 +397,11 @@ module Vmpooler
|
||||||
if (available == 1)
|
if (available == 1)
|
||||||
result['ok'] = true
|
result['ok'] = true
|
||||||
|
|
||||||
params[:template].split('+').each do |template|
|
payload.each do |key, val|
|
||||||
result[template] ||= {}
|
val.to_i.times do |_i|
|
||||||
|
result = checkout_vm(key, result)
|
||||||
vm = backend.spop('vmpooler__ready__' + template)
|
|
||||||
|
|
||||||
unless vm.nil?
|
|
||||||
backend.sadd('vmpooler__running__' + template, vm)
|
|
||||||
backend.hset('vmpooler__active__' + template, vm, Time.now)
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'checkout', Time.now)
|
|
||||||
|
|
||||||
if Vmpooler::API.settings.config[:auth] and has_token?
|
|
||||||
validate_token(backend)
|
|
||||||
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'token:token', request.env['HTTP_X_AUTH_TOKEN'])
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'token:user',
|
|
||||||
backend.hget('vmpooler__token__' + request.env['HTTP_X_AUTH_TOKEN'], 'user')
|
|
||||||
)
|
|
||||||
|
|
||||||
if config['vm_lifetime_auth'].to_i > 0
|
|
||||||
backend.hset('vmpooler__vm__' + vm, 'lifetime', config['vm_lifetime_auth'].to_i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
result[template] ||= {}
|
|
||||||
|
|
||||||
if result[template]['hostname']
|
|
||||||
result[template]['hostname'] = [result[template]['hostname']] unless result[template]['hostname'].is_a?(Array)
|
|
||||||
result[template]['hostname'].push(vm)
|
|
||||||
else
|
|
||||||
result[template]['hostname'] = vm
|
|
||||||
end
|
|
||||||
else
|
|
||||||
status 503
|
|
||||||
result['ok'] = false
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
|
||||||
status 503
|
|
||||||
result['ok'] = false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if result['ok'] && config['domain']
|
if result['ok'] && config['domain']
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,8 @@ describe Vmpooler::API::V1 do
|
||||||
pools: [
|
pools: [
|
||||||
{'name' => 'pool1', 'size' => 5},
|
{'name' => 'pool1', 'size' => 5},
|
||||||
{'name' => 'pool2', 'size' => 10}
|
{'name' => 'pool2', 'size' => 10}
|
||||||
]
|
],
|
||||||
|
alias: { 'poolone' => 'pool1' }
|
||||||
} }
|
} }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
|
@ -221,6 +222,31 @@ describe Vmpooler::API::V1 do
|
||||||
expect_json(ok = true, http = 200)
|
expect_json(ok = true, http = 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns a single VM for an alias' do
|
||||||
|
expect(redis).to receive(:exists).with("vmpooler__ready__poolone").and_return(false)
|
||||||
|
|
||||||
|
post "#{prefix}/vm", '{"poolone":"1"}'
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails on nonexistant pools' do
|
||||||
|
expect(redis).to receive(:exists).with("vmpooler__ready__poolpoolpool").and_return(false)
|
||||||
|
|
||||||
|
post "#{prefix}/vm", '{"poolpoolpool":"1"}'
|
||||||
|
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns multiple VMs' do
|
it 'returns multiple VMs' do
|
||||||
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
||||||
|
|
||||||
|
|
@ -304,6 +330,158 @@ describe Vmpooler::API::V1 do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '/vm/:template' do
|
||||||
|
let(:redis) { double('redis') }
|
||||||
|
let(:prefix) { '/api/v1' }
|
||||||
|
let(:config) { {
|
||||||
|
config: {
|
||||||
|
'site_name' => 'test pooler',
|
||||||
|
'vm_lifetime_auth' => 2
|
||||||
|
},
|
||||||
|
pools: [
|
||||||
|
{'name' => 'pool1', 'size' => 5},
|
||||||
|
{'name' => 'pool2', 'size' => 10}
|
||||||
|
],
|
||||||
|
alias: { 'poolone' => 'pool1' }
|
||||||
|
} }
|
||||||
|
|
||||||
|
before do
|
||||||
|
app.settings.set :config, config
|
||||||
|
app.settings.set :redis, redis
|
||||||
|
|
||||||
|
allow(redis).to receive(:exists).and_return '1'
|
||||||
|
allow(redis).to receive(:hget).with('vmpooler__token__abcdefghijklmnopqrstuvwxyz012345', 'user').and_return 'jdoe'
|
||||||
|
allow(redis).to receive(:hset).and_return '1'
|
||||||
|
allow(redis).to receive(:sadd).and_return '1'
|
||||||
|
allow(redis).to receive(:scard).and_return '5'
|
||||||
|
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
|
||||||
|
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return 'qrstuvwxyz012345'
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /vm/:template' do
|
||||||
|
it 'returns a single VM' do
|
||||||
|
post "#{prefix}/vm/pool1", ''
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns a single VM for an alias' do
|
||||||
|
expect(redis).to receive(:exists).with("vmpooler__ready__poolone").and_return(false)
|
||||||
|
|
||||||
|
post "#{prefix}/vm/poolone", ''
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails on nonexistant pools' do
|
||||||
|
expect(redis).to receive(:exists).with("vmpooler__ready__poolpoolpool").and_return(false)
|
||||||
|
|
||||||
|
post "#{prefix}/vm/poolpoolpool", ''
|
||||||
|
|
||||||
|
expect_json(ok = false, http = 404)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns multiple VMs' do
|
||||||
|
post "#{prefix}/vm/pool1+pool2", ''
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
},
|
||||||
|
pool2: {
|
||||||
|
hostname: 'qrstuvwxyz012345'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
context '(auth not configured)' do
|
||||||
|
let(:config) { { auth: false } }
|
||||||
|
|
||||||
|
it 'does not extend VM lifetime if auth token is provided' do
|
||||||
|
expect(redis).not_to receive(:hset).with("vmpooler__vm__abcdefghijklmnop", "lifetime", 2)
|
||||||
|
|
||||||
|
post "#{prefix}/vm/pool1", '', {
|
||||||
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context '(auth configured)' do
|
||||||
|
let(:config) { { auth: true } }
|
||||||
|
|
||||||
|
it 'extends VM lifetime if auth token is provided' do
|
||||||
|
expect(redis).to receive(:hset).with("vmpooler__vm__abcdefghijklmnop", "lifetime", 2).once
|
||||||
|
|
||||||
|
post "#{prefix}/vm/pool1", '', {
|
||||||
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
||||||
|
}
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not extend VM lifetime if auth token is not provided' do
|
||||||
|
expect(redis).not_to receive(:hset).with("vmpooler__vm__abcdefghijklmnop", "lifetime", 2)
|
||||||
|
|
||||||
|
post "#{prefix}/vm/pool1", ''
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
ok: true,
|
||||||
|
pool1: {
|
||||||
|
hostname: 'abcdefghijklmnop'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
||||||
|
|
||||||
|
expect_json(ok = true, http = 200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '/vm/:hostname' do
|
describe '/vm/:hostname' do
|
||||||
let(:redis) { double('redis') }
|
let(:redis) { double('redis') }
|
||||||
let(:prefix) { '/api/v1' }
|
let(:prefix) { '/api/v1' }
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,10 @@
|
||||||
# The name of the pool.
|
# The name of the pool.
|
||||||
# (required)
|
# (required)
|
||||||
#
|
#
|
||||||
|
# - alias
|
||||||
|
# Other names this pool can be requested as.
|
||||||
|
# (optional)
|
||||||
|
#
|
||||||
# - template
|
# - template
|
||||||
# The template or virtual machine target to spawn clones from.
|
# The template or virtual machine target to spawn clones from.
|
||||||
# (required)
|
# (required)
|
||||||
|
|
@ -221,6 +225,7 @@
|
||||||
|
|
||||||
:pools:
|
:pools:
|
||||||
- name: 'debian-7-i386'
|
- name: 'debian-7-i386'
|
||||||
|
alias: [ 'debian-7-32' ]
|
||||||
template: 'Templates/debian-7-i386'
|
template: 'Templates/debian-7-i386'
|
||||||
folder: 'Pooled VMs/debian-7-i386'
|
folder: 'Pooled VMs/debian-7-i386'
|
||||||
datastore: 'vmstorage'
|
datastore: 'vmstorage'
|
||||||
|
|
@ -228,6 +233,7 @@
|
||||||
timeout: 15
|
timeout: 15
|
||||||
ready_ttl: 1440
|
ready_ttl: 1440
|
||||||
- name: 'debian-7-x86_64'
|
- name: 'debian-7-x86_64'
|
||||||
|
alias: [ 'debian-7-64', 'debian-7-amd64' ]
|
||||||
template: 'Templates/debian-7-x86_64'
|
template: 'Templates/debian-7-x86_64'
|
||||||
folder: 'Pooled VMs/debian-7-x86_64'
|
folder: 'Pooled VMs/debian-7-x86_64'
|
||||||
datastore: 'vmstorage'
|
datastore: 'vmstorage'
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue