mirror of
https://github.com/puppetlabs/vmpooler.git
synced 2026-01-26 01:58:41 -05:00
444 lines
15 KiB
Ruby
444 lines
15 KiB
Ruby
require 'spec_helper'
|
|
require 'rack/test'
|
|
|
|
describe Vmpooler::API::V2 do
|
|
include Rack::Test::Methods
|
|
|
|
def app()
|
|
Vmpooler::API
|
|
end
|
|
|
|
# Added to ensure no leakage in rack state from previous tests.
|
|
# Removes all routes, filters, middleware and extension hooks from the current class
|
|
# https://rubydoc.info/gems/sinatra/Sinatra/Base#reset!-class_method
|
|
before(:each) do
|
|
app.reset!
|
|
end
|
|
|
|
describe '/vm' do
|
|
let(:prefix) { '/api/v2' }
|
|
let(:metrics) { Vmpooler::Metrics::DummyStatsd.new }
|
|
let(:config) {
|
|
{
|
|
config: {
|
|
'site_name' => 'test pooler',
|
|
'vm_lifetime_auth' => 2
|
|
},
|
|
pools: [
|
|
{'name' => 'pool1', 'size' => 5, 'domain' => 'one.example.com'},
|
|
{'name' => 'pool2', 'size' => 10, 'domain' => 'two.example.com'},
|
|
{'name' => 'pool3', 'size' => 10, 'domain' => 'three.example.com'}
|
|
],
|
|
statsd: { 'prefix' => 'stats_prefix'},
|
|
alias: { 'poolone' => ['pool1'] },
|
|
pool_names: [ 'pool1', 'pool2', 'pool3', 'poolone', 'genericpool' ]
|
|
}
|
|
}
|
|
let(:current_time) { Time.now }
|
|
let(:vmname) { 'abcdefghijkl' }
|
|
let(:checkoutlock) { Mutex.new }
|
|
|
|
before(:each) do
|
|
expect(app).to receive(:run!).once
|
|
app.execute([:api], config, redis, metrics, nil)
|
|
app.settings.set :config, auth: false
|
|
app.settings.set :checkoutlock, checkoutlock
|
|
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', vmname, redis
|
|
expect(TCPSocket).to receive(:gethostbyname).and_raise(RuntimeError)
|
|
get "#{prefix}/vm/#{vmname}"
|
|
expect_json(ok = true, http = 200)
|
|
response_body = (JSON.parse(last_response.body)[vmname])
|
|
|
|
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("")
|
|
expect(response_body["host"]).to eq("host1")
|
|
end
|
|
end
|
|
|
|
describe 'POST /vm' do
|
|
|
|
let(:socket) { double('socket') }
|
|
it 'returns a single VM' do
|
|
create_ready_vm 'pool1', vmname, redis
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: "#{vmname}.one.example.com"
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
end
|
|
|
|
it 'returns a single VM for an alias' do
|
|
create_ready_vm 'pool1', vmname, redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"poolone":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
poolone: {
|
|
hostname: "#{vmname}.one.example.com"
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
|
|
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
|
|
app.settings.config.delete(:alias)
|
|
app.settings.config[:pool_names] = ['pool1', 'pool2']
|
|
|
|
create_ready_vm 'pool1', vmname, redis
|
|
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', vmname, redis
|
|
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', vmname, redis
|
|
create_ready_vm 'pool2', 'qrstuvwxyz012345', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: "#{vmname}.one.example.com"
|
|
},
|
|
domain: 'two.example.com',
|
|
pool2: {
|
|
hostname: 'qrstuvwxyz012345.two.example.com'
|
|
}
|
|
}
|
|
|
|
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', redis
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop', redis
|
|
create_ready_vm 'pool2', 'qrstuvwxyz012345', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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.one.example.com', '2abcdefghijklmnop.one.example.com')
|
|
expect(result['pool2']['hostname']).to eq('qrstuvwxyz012345.two.example.com')
|
|
|
|
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', redis
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop', redis
|
|
create_ready_vm 'pool2', '1qrstuvwxyz012345', redis
|
|
create_ready_vm 'pool2', '2qrstuvwxyz012345', redis
|
|
create_ready_vm 'pool2', '3qrstuvwxyz012345', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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.one.example.com', '2abcdefghijklmnop.one.example.com')
|
|
expect(result['pool2']['hostname']).to include('1qrstuvwxyz012345.two.example.com', '2qrstuvwxyz012345.two.example.com', '3qrstuvwxyz012345.two.example.com')
|
|
|
|
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', redis
|
|
create_ready_vm 'pool2', '2abcdefghijklmnop', redis
|
|
create_ready_vm 'pool3', '1qrstuvwxyz012345', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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.one.example.com', '2abcdefghijklmnop.two.example.com', '1qrstuvwxyz012345.three.example.com')
|
|
|
|
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', redis
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop', redis
|
|
create_ready_vm 'pool1', '3abcdefghijklmnop', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
|
|
expected = {
|
|
ok: true,
|
|
"pool1": {
|
|
"hostname": "1abcdefghijklmnop.one.example.com"
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
|
|
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', redis
|
|
|
|
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', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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', redis)).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', redis
|
|
|
|
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', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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', redis)).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', redis
|
|
|
|
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', redis
|
|
create_ready_vm 'pool1', '2abcdefghijklmnop', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
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', redis)).to eq(true)
|
|
expect(pool_has_ready_vm?('pool1', '2abcdefghijklmnop', redis)).to eq(true)
|
|
end
|
|
|
|
it 'returns the second VM when the first fails to respond' do
|
|
create_ready_vm 'pool1', vmname, redis
|
|
create_ready_vm 'pool1', "2#{vmname}", redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with(vmname, 'one.example.com').and_raise('mockerror')
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).with("2#{vmname}", 'one.example.com').and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: "2#{vmname}.one.example.com"
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
|
|
expect(last_response.body).to eq(JSON.pretty_generate(expected))
|
|
|
|
expect(pool_has_ready_vm?('pool1', vmname, redis)).to be false
|
|
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', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}', {
|
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
|
}
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop.one.example.com'
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
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', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}', {
|
|
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
|
|
}
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop.one.example.com'
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
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', redis
|
|
|
|
allow_any_instance_of(Vmpooler::API::Helpers).to receive(:open_socket).and_return(socket)
|
|
|
|
post "#{prefix}/vm", '{"pool1":"1"}'
|
|
expect_json(ok = true, http = 200)
|
|
|
|
expected = {
|
|
ok: true,
|
|
pool1: {
|
|
hostname: 'abcdefghijklmnop.one.example.com'
|
|
},
|
|
domain: 'one.example.com'
|
|
}
|
|
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
|