mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 10:08:40 -05:00
This commit updates how a VM is checked out to ensure that there is no window where the VM could be considered discovered, and therefore destroyed. Without this change the VM is retrieved by calling 'spop' on the ready queue, and then adding it to the running queue. This change moves to selecting the VM by retrieving the last member of the set, and moving it with 'smove' from ready to running. As a result of this change vmpooler moves from retrieving the VMs from the ready state randomly, to instead retrieve the oldest VM in the queue. This change should reduce churn where it would otherwise not be required to satisfy demand.
376 lines
12 KiB
Ruby
376 lines
12 KiB
Ruby
require 'spec_helper'
|
|
require 'rack/test'
|
|
|
|
describe Vmpooler::API::V1 do
|
|
include Rack::Test::Methods
|
|
|
|
def app()
|
|
Vmpooler::API
|
|
end
|
|
|
|
describe '/vm' do
|
|
let(:prefix) { '/api/v1' }
|
|
let(:metrics) { Vmpooler::DummyStatsd.new }
|
|
let(:config) {
|
|
{
|
|
config: {
|
|
'site_name' => 'test pooler',
|
|
'vm_lifetime_auth' => 2,
|
|
},
|
|
pools: [
|
|
{'name' => 'pool1', 'size' => 5},
|
|
{'name' => 'pool2', 'size' => 10},
|
|
{'name' => 'pool3', 'size' => 10}
|
|
],
|
|
statsd: { 'prefix' => 'stats_prefix'},
|
|
alias: { 'poolone' => ['pool1'] },
|
|
pool_names: [ 'pool1', 'pool2', 'pool3', 'poolone', 'genericpool' ]
|
|
}
|
|
}
|
|
let(:current_time) { Time.now }
|
|
|
|
before(:each) do
|
|
app.settings.set :config, config
|
|
app.settings.set :redis, redis
|
|
app.settings.set :metrics, metrics
|
|
app.settings.set :config, auth: false
|
|
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
|
|
end
|
|
|
|
describe 'GET /vm/:hostname' do
|
|
it 'returns correct information on a running vm' do
|
|
create_running_vm 'pool1', 'abcdefghijklmnop'
|
|
get "#{prefix}/vm/abcdefghijklmnop"
|
|
expect_json(ok = true, http = 200)
|
|
response_body = (JSON.parse(last_response.body)["abcdefghijklmnop"])
|
|
|
|
expect(response_body["template"]).to eq("pool1")
|
|
expect(response_body["lifetime"]).to eq(0)
|
|
expect(response_body["running"]).to be >= 0
|
|
expect(response_body["remaining"]).to be <= 0
|
|
expect(response_body["start_time"]).to eq(current_time.to_datetime.rfc3339)
|
|
expect(response_body["end_time"]).to eq(current_time.to_datetime.rfc3339)
|
|
expect(response_body["state"]).to eq("running")
|
|
expect(response_body["ip"]).to eq("")
|
|
end
|
|
end
|
|
|
|
describe 'POST /vm' do
|
|
it 'returns a single VM' do
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop'
|
|
}
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
end
|
|
|
|
it 'returns a single VM for an alias' do
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"poolone":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
poolone: {
|
|
hostname: 'abcdefghijklmnop'
|
|
}
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
end
|
|
|
|
it 'fails on nonexistant pools' do
|
|
post "#{prefix}/vm", '{"poolpoolpool":"1"}'
|
|
expect_json(ok = false, http = 404)
|
|
end
|
|
|
|
it 'returns 503 for empty pool when aliases are not defined' do
|
|
Vmpooler::API.settings.config.delete(:alias)
|
|
Vmpooler::API.settings.config[:pool_names] = ['pool1', 'pool2']
|
|
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
post "#{prefix}/vm/pool1"
|
|
post "#{prefix}/vm/pool1"
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
end
|
|
|
|
it 'returns 503 for empty pool referenced by alias' do
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
post "#{prefix}/vm/poolone"
|
|
post "#{prefix}/vm/poolone"
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
end
|
|
|
|
it 'returns multiple VMs' do
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
create_ready_vm 'pool2', 'qrstuvwxyz012345'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop'
|
|
},
|
|
pool2: {
|
|
hostname: 'qrstuvwxyz012345'
|
|
}
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
end
|
|
|
|
it 'returns multiple VMs even when multiple instances from the same pool are requested' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop'
|
|
create_ready_vm 'pool2', 'qrstuvwxyz012345'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}'
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop' ]
|
|
},
|
|
pool2: {
|
|
hostname: 'qrstuvwxyz012345'
|
|
}
|
|
}
|
|
|
|
result = JSON.parse(last_response.body)
|
|
expect(result['ok']).to eq(true)
|
|
expect(result['pool1']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop')
|
|
expect(result['pool2']['hostname']).to eq('qrstuvwxyz012345')
|
|
|
|
expect_json(ok = true, http = 200)
|
|
end
|
|
|
|
it 'returns multiple VMs even when multiple instances from multiple pools are requested' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop'
|
|
create_ready_vm 'pool2', '1qrstuvwxyz012345'
|
|
create_ready_vm 'pool2', '2qrstuvwxyz012345'
|
|
create_ready_vm 'pool2', '3qrstuvwxyz012345'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}'
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop' ]
|
|
},
|
|
pool2: {
|
|
hostname: [ '1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345' ]
|
|
}
|
|
}
|
|
|
|
result = JSON.parse(last_response.body)
|
|
expect(result['ok']).to eq(true)
|
|
expect(result['pool1']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop')
|
|
expect(result['pool2']['hostname']).to include('1qrstuvwxyz012345', '2qrstuvwxyz012345', '3qrstuvwxyz012345')
|
|
|
|
expect_json(ok = true, http = 200)
|
|
end
|
|
|
|
it 'returns VMs from multiple backend pools requested by an alias' do
|
|
Vmpooler::API.settings.config[:alias]['genericpool'] = ['pool1', 'pool2', 'pool3']
|
|
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
create_ready_vm 'pool2', '2abcdefghijklmnop'
|
|
create_ready_vm 'pool3', '1qrstuvwxyz012345'
|
|
|
|
post "#{prefix}/vm", '{"genericpool":"3"}'
|
|
|
|
expected = {
|
|
ok: true,
|
|
genericpool: {
|
|
hostname: [ '1abcdefghijklmnop', '2abcdefghijklmnop', '1qrstuvwxyz012345' ]
|
|
}
|
|
}
|
|
|
|
result = JSON.parse(last_response.body)
|
|
expect(result['ok']).to eq(true)
|
|
expect(result['genericpool']['hostname']).to include('1abcdefghijklmnop', '2abcdefghijklmnop', '1qrstuvwxyz012345')
|
|
|
|
expect_json(ok = true, http = 200)
|
|
end
|
|
|
|
it 'returns the first VM that was moved to the ready state when checking out a VM' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop'
|
|
create_ready_vm 'pool1', '3abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
|
|
expected = {
|
|
ok: true,
|
|
"pool1": {
|
|
"hostname": "1abcdefghijklmnop"
|
|
}
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = true, http = 200)
|
|
end
|
|
|
|
it 'fails when not all requested vms can be allocated' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
end
|
|
|
|
it 'returns any checked out vms to their pools when not all requested vms can be allocated' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
|
|
expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
|
|
end
|
|
|
|
it 'fails when not all requested vms can be allocated, when requesting multiple instances from a pool' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
end
|
|
|
|
it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from a pool' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
|
|
expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
|
|
end
|
|
|
|
it 'fails when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
end
|
|
|
|
it 'returns any checked out vms to their pools when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do
|
|
create_ready_vm 'pool1', '1abcdefghijklmnop'
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}'
|
|
|
|
expected = { ok: false }
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
expect_json(ok = false, http = 503)
|
|
|
|
expect(pool_has_ready_vm?('pool1', '1abcdefghijklmnop')).to eq(true)
|
|
expect(pool_has_ready_vm?('pool1', '2abcdefghijklmnop')).to eq(true)
|
|
end
|
|
|
|
context '(auth not configured)' do
|
|
it 'does not extend VM lifetime if auth token is provided' do
|
|
app.settings.set :config, auth: false
|
|
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}', {
|
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
|
}
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop'
|
|
}
|
|
}
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
|
|
vm = fetch_vm('abcdefghijklmnop')
|
|
expect(vm['lifetime']).to be_nil
|
|
end
|
|
end
|
|
|
|
context '(auth configured)' do
|
|
it 'extends VM lifetime if auth token is provided' do
|
|
app.settings.set :config, auth: true
|
|
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}', {
|
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
|
}
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop'
|
|
}
|
|
}
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
|
|
vm = fetch_vm('abcdefghijklmnop')
|
|
expect(vm['lifetime'].to_i).to eq(2)
|
|
end
|
|
|
|
it 'does not extend VM lifetime if auth token is not provided' do
|
|
app.settings.set :config, auth: true
|
|
create_ready_vm 'pool1', 'abcdefghijklmnop'
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop'
|
|
}
|
|
}
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
|
|
vm = fetch_vm('abcdefghijklmnop')
|
|
expect(vm['lifetime']).to be_nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|