Merge remote-tracking branch 'origin/master' into merge-master-into-ci.next

* origin/master:
  Added IP lookup functionality for /vm/hostname (#154)
  Add info about vmfloaty
  Improved tests for vmpooler (#152)
This commit is contained in:
Josh Cooper 2016-07-08 11:50:16 -07:00
commit 28e84e542d
12 changed files with 952 additions and 1000 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.ruby-version
Gemfile.lock
vendor

View file

@ -1,6 +1,8 @@
cache: bundler cache: bundler
sudo: false sudo: false
language: ruby language: ruby
services:
- redis-server
rvm: rvm:
- 1.9.3 - 1.9.3
- 2.1.1 - 2.1.1

View file

@ -80,7 +80,8 @@ A dashboard is provided to offer real-time statistics and historical graphs. It
## Command-line Utility ## Command-line Utility
The [vmpooler_client.py](https://github.com/puppetlabs/vmpooler-client) CLI utility provides easy access to the vmpooler service. The tool is cross-platform and written in Python. - The [vmpooler_client.py](https://github.com/puppetlabs/vmpooler-client) CLI utility provides easy access to the vmpooler service. The tool is cross-platform and written in Python.
- [vmfloaty](https://github.com/briancain/vmfloaty) is a ruby based CLI tool and scripting library written in ruby.
## Build status ## Build status

View file

@ -457,6 +457,15 @@ module Vmpooler
result[params[:hostname]]['disk'] = rdata['disk'].split(':') result[params[:hostname]]['disk'] = rdata['disk'].split(':')
end end
# Look up IP address of the hostname
begin
ipAddress = TCPSocket.gethostbyname(params[:hostname])[3]
rescue
ipAddress = ""
end
result[params[:hostname]]['ip'] = ipAddress
if config['domain'] if config['domain']
result[params[:hostname]]['domain'] = config['domain'] result[params[:hostname]]['domain'] = config['domain']
end end

View file

@ -1,7 +1,12 @@
def expect_json( def redis
ok = true, unless @redis
http = 200 @redis = Redis.new
) @redis.select(15) # let's use the highest numbered database available in a default install
end
@redis
end
def expect_json(ok = true, http = 200)
expect(last_response.header['Content-Type']).to eq('application/json') expect(last_response.header['Content-Type']).to eq('application/json')
if (ok == true) then if (ok == true) then
@ -12,3 +17,59 @@ def expect_json(
expect(last_response.status).to eq(http) expect(last_response.status).to eq(http)
end end
def create_token(token, user, timestamp)
redis.hset("vmpooler__token__#{token}", 'user', user)
redis.hset("vmpooler__token__#{token}", 'created', timestamp)
end
def get_token_data(token)
redis.hgetall("vmpooler__token__#{token}")
end
def token_exists?(token)
result = get_token_data
result && !result.empty?
end
def create_ready_vm(template, name, token = nil)
create_vm(name, token)
redis.sadd("vmpooler__ready__#{template}", name)
redis.hset("vmpooler_vm_#{name}", "template", template)
end
def create_running_vm(template, name, token = nil)
create_vm(name, token)
redis.sadd("vmpooler__running__#{template}", name)
redis.hset("vmpooler__vm__#{name}", "template", template)
end
def create_vm(name, token = nil)
redis.hset("vmpooler__vm__#{name}", 'checkout', Time.now)
if token
redis.hset("vmpooler__vm__#{name}", 'token:token', token)
end
end
def fetch_vm(vm)
redis.hgetall("vmpooler__vm__#{vm}")
end
def snapshot_vm(vm, snapshot = '12345678901234567890123456789012')
redis.sadd('vmpooler__tasks__snapshot', "#{vm}:#{snapshot}")
redis.hset("vmpooler__vm__#{vm}", "snapshot:#{snapshot}", "1")
end
def has_vm_snapshot?(vm)
redis.smembers('vmpooler__tasks__snapshot').any? do |snapshot|
instance, sha = snapshot.split(':')
vm == instance
end
end
def vm_reverted_to_snapshot?(vm, snapshot = nil)
redis.smembers('vmpooler__tasks__snapshot-revert').any? do |action|
instance, sha = action.split(':')
instance == vm and (snapshot ? (sha == snapshot) : true)
end
end

View file

@ -2,3 +2,4 @@ require 'helpers'
require 'rbvmomi' require 'rbvmomi'
require 'rspec' require 'rspec'
require 'vmpooler' require 'vmpooler'
require 'redis'

View file

@ -0,0 +1,173 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/token' do
let(:prefix) { '/api/v1' }
let(:current_time) { Time.now }
let(:config) { { } }
before do
app.settings.set :config, config
app.settings.set :redis, redis
end
describe 'GET /token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
get "#{prefix}/token"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
get "#{prefix}/token"
expect_json(ok = false, http = 401)
end
it 'returns a list of tokens if authed' do
create_token "abc", "admin", current_time
authorize 'admin', 's3cr3t'
get "#{prefix}/token"
expect_json(ok = true, http = 200)
expect(JSON.parse(last_response.body)['abc']['created']).to eq(current_time.to_s)
end
end
end
describe 'POST /token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
post "#{prefix}/token"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
post "#{prefix}/token"
expect_json(ok = false, http = 401)
end
it 'returns a newly created token if authed' do
authorize 'admin', 's3cr3t'
post "#{prefix}/token"
expect_json(ok = true, http = 200)
returned_token = JSON.parse(last_response.body)['token']
expect(returned_token.length).to be(32)
expect(get_token_data(returned_token)['user']).to eq("admin")
end
end
end
end
describe '/token/:token' do
let(:prefix) { '/api/v1' }
let(:current_time) { Time.now }
before do
app.settings.set :config, config
app.settings.set :redis, redis
end
def create_vm_for_token(token, pool, vm)
redis.sadd("vmpooler__running__#{pool}", vm)
redis.hset("vmpooler__vm__#{vm}", "token:token", token)
end
describe 'GET /token/:token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
get "#{prefix}/token/this"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { {
auth: true,
pools: [
{'name' => 'pool1', 'size' => 5}
]
} }
it 'returns a token' do
create_token "mytoken", "admin", current_time
create_vm_for_token "mytoken", "pool1", "vmhostname"
get "#{prefix}/token/mytoken"
expect_json(ok = true, http = 200)
expect(JSON.parse(last_response.body)['ok']).to eq(true)
expect(JSON.parse(last_response.body)['mytoken']['user']).to eq('admin')
expect(JSON.parse(last_response.body)['mytoken']['vms']['running']).to include('vmhostname')
end
end
end
describe 'DELETE /token/:token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
delete "#{prefix}/token/this"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
delete "#{prefix}/token/this"
expect_json(ok = false, http = 401)
end
it 'deletes a token if authed' do
create_token("mytoken", "admin", current_time)
authorize 'admin', 's3cr3t'
delete "#{prefix}/token/mytoken"
expect_json(ok = true, http = 200)
end
it 'fails if token does not exist' do
authorize 'admin', 's3cr3t'
delete "#{prefix}/token/missingtoken"
expect_json(ok = false, http = 401) # TODO: should this be 404?
end
end
end
end
end

View file

@ -0,0 +1,317 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
def has_set_tag?(vm, tag, value)
value == redis.hget("vmpooler__vm__#{vm}", "tag:#{tag}")
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/vm/:hostname' do
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' },
}
}
let(:current_time) { Time.now }
before(:each) do
redis.flushdb
app.settings.set :config, config
app.settings.set :redis, redis
app.settings.set :config, auth: false
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
end
describe 'PUT /vm/:hostname' do
it 'allows tags to be set' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":"rspec"}}'
expect_json(ok = true, http = 200)
expect has_set_tag?('testhost', 'tested_by', 'rspec')
end
it 'skips empty tags' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":""}}'
expect_json(ok = true, http = 200)
expect !has_set_tag?('testhost', 'tested_by', '')
end
it 'does not set tags if request body format is invalid' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"tested"}}'
expect_json(ok = false, http = 400)
expect !has_set_tag?('testhost', 'tested', '')
end
context '(allowed_tags configured)' do
it 'fails if specified tag is not in allowed_tags array' do
app.settings.set :config,
{ :config => { 'allowed_tags' => ['created_by', 'project', 'url'] } }
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"created_by":"rspec","tested_by":"rspec"}}'
expect_json(ok = false, http = 400)
expect !has_set_tag?('testhost', 'tested_by', 'rspec')
end
end
context '(tagfilter configured)' do
let(:config) { {
tagfilter: { 'url' => '(.*)\/' },
} }
it 'correctly filters tags' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com/something.html"}}'
expect_json(ok = true, http = 200)
expect has_set_tag?('testhost', 'url', 'foo.com')
end
it "doesn't eat tags not matching filter" do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com"}}'
expect_json(ok = true, http = 200)
expect has_set_tag?('testhost', 'url', 'foo.com')
end
end
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'allows VM lifetime to be modified without a token' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = true, http = 200)
vm = fetch_vm('testhost')
expect(vm['lifetime'].to_i).to eq(1)
end
it 'does not allow a lifetime to be 0' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"lifetime":"0"}'
expect_json(ok = false, http = 400)
vm = fetch_vm('testhost')
expect(vm['lifetime']).to be_nil
end
end
context '(auth configured)' do
before(:each) do
app.settings.set :config, auth: true
end
it 'allows VM lifetime to be modified with a token' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"lifetime":"1"}', {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 200)
vm = fetch_vm('testhost')
expect(vm['lifetime'].to_i).to eq(1)
end
it 'does not allows VM lifetime to be modified without a token' do
create_vm('testhost')
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = false, http = 401)
end
end
end
describe 'DELETE /vm/:hostname' do
context '(auth not configured)' do
it 'does not delete a non-existant VM' do
delete "#{prefix}/vm/testhost"
expect_json(ok = false, http = 404)
end
it 'deletes an existing VM' do
create_running_vm('pool1', 'testhost')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
expect_json(ok = true, http = 200)
expect !fetch_vm('testhost')
end
end
context '(auth configured)' do
before(:each) do
app.settings.set :config, auth: true
end
context '(checked-out without token)' do
it 'deletes a VM without supplying a token' do
create_running_vm('pool1', 'testhost')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
expect_json(ok = true, http = 200)
expect !fetch_vm('testhost')
end
end
context '(checked-out with token)' do
it 'fails to delete a VM without supplying a token' do
create_running_vm('pool1', 'testhost', 'abcdefghijklmnopqrstuvwxyz012345')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost"
expect_json(ok = false, http = 401)
expect fetch_vm('testhost')
end
it 'deletes a VM when token is supplied' do
create_running_vm('pool1', 'testhost', 'abcdefghijklmnopqrstuvwxyz012345')
expect fetch_vm('testhost')
delete "#{prefix}/vm/testhost", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 200)
expect !fetch_vm('testhost')
end
end
end
end
describe 'POST /vm/:hostname/snapshot' do
context '(auth not configured)' do
it 'creates a snapshot' do
create_vm('testhost')
post "#{prefix}/vm/testhost/snapshot"
expect_json(ok = true, http = 202)
expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32)
end
end
context '(auth configured)' do
before(:each) do
app.settings.set :config, auth: true
end
it 'returns a 401 if not authed' do
post "#{prefix}/vm/testhost/snapshot"
expect_json(ok = false, http = 401)
expect !has_vm_snapshot?('testhost')
end
it 'creates a snapshot if authed' do
create_vm('testhost')
snapshot_vm('testhost', 'testsnapshot')
post "#{prefix}/vm/testhost/snapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 202)
expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32)
expect has_vm_snapshot?('testhost')
end
end
end
describe 'POST /vm/:hostname/snapshot/:snapshot' do
context '(auth not configured)' do
it 'reverts to a snapshot' do
create_vm('testhost')
snapshot_vm('testhost', 'testsnapshot')
post "#{prefix}/vm/testhost/snapshot/testsnapshot"
expect_json(ok = true, http = 202)
expect vm_reverted_to_snapshot?('testhost', 'testsnapshot')
end
it 'fails if the specified snapshot does not exist' do
create_vm('testhost')
post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = false, http = 404)
expect !vm_reverted_to_snapshot?('testhost', 'testsnapshot')
end
end
context '(auth configured)' do
before(:each) do
app.settings.set :config, auth: true
end
it 'returns a 401 if not authed' do
create_vm('testhost')
snapshot_vm('testhost', 'testsnapshot')
post "#{prefix}/vm/testhost/snapshot/testsnapshot"
expect_json(ok = false, http = 401)
expect !vm_reverted_to_snapshot?('testhost', 'testsnapshot')
end
it 'fails if authed and the specified snapshot does not exist' do
create_vm('testhost')
post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = false, http = 404)
expect !vm_reverted_to_snapshot?('testhost', 'testsnapshot')
end
it 'reverts to a snapshot if authed' do
create_vm('testhost')
snapshot_vm('testhost', 'testsnapshot')
post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 202)
expect vm_reverted_to_snapshot?('testhost', 'testsnapshot')
end
end
end
end
end

View file

@ -0,0 +1,175 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/vm' do
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' },
}
}
let(:current_time) { Time.now }
before(:each) do
redis.flushdb
app.settings.set :config, config
app.settings.set :redis, redis
app.settings.set :config, auth: false
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
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,
pool1: {
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 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
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

View file

@ -0,0 +1,176 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/vm/:template' do
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' },
}
}
let(:current_time) { Time.now }
before(:each) do
redis.flushdb
app.settings.set :config, config
app.settings.set :redis, redis
app.settings.set :config, auth: false
create_token('abcdefghijklmnopqrstuvwxyz012345', 'jdoe', current_time)
end
describe 'POST /vm/:template' do
it 'returns a single VM' do
create_ready_vm 'pool1', 'abcdefghijklmnop'
post "#{prefix}/vm/pool1", ''
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", ''
expected = {
ok: true,
pool1: {
hostname: 'abcdefghijklmnop'
}
}
expect_json(ok = true, http = 200)
expect(last_response.body).to eq(JSON.pretty_generate(expected))
end
it 'fails on nonexistant pools' do
post "#{prefix}/vm/poolpoolpool", ''
expect_json(ok = false, http = 404)
end
it 'returns multiple VMs' do
create_ready_vm 'pool1', 'abcdefghijklmnop'
create_ready_vm 'pool2', 'qrstuvwxyz012345'
post "#{prefix}/vm/pool1+pool2", ''
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
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", '', {
'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", '', {
'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", ''
expected = {
ok: true,
pool1: {
hostname: 'abcdefghijklmnop'
}
}
expect_json(ok = true, http = 200)
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

View file

@ -1,963 +0,0 @@
require 'spec_helper'
require 'rack/test'
module Vmpooler
class API
module Helpers
def authenticate(auth, username_str, password_str)
username_str == 'admin' and password_str == 's3cr3t'
end
end
end
end
describe Vmpooler::API::V1 do
include Rack::Test::Methods
def app()
Vmpooler::API
end
describe '/token' do
let(:redis) { double('redis') }
let(:prefix) { '/api/v1' }
before do
app.settings.set :config, config
app.settings.set :redis, redis
end
describe 'GET /token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
get "#{prefix}/token"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
get "#{prefix}/token"
expect_json(ok = false, http = 401)
end
it 'returns a list of tokens if authed' do
expect(redis).to receive(:keys).with('vmpooler__token__*').and_return(["vmpooler__token__abc"])
expect(redis).to receive(:hgetall).with('vmpooler__token__abc').and_return({"user" => "admin", "created" => "now"})
authorize 'admin', 's3cr3t'
get "#{prefix}/token"
expect(JSON.parse(last_response.body)['abc']['created']).to eq('now')
expect_json(ok = true, http = 200)
end
end
end
describe 'POST /token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
post "#{prefix}/token"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
before do
allow(redis).to receive(:hset).and_return '1'
end
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
post "#{prefix}/token"
expect_json(ok = false, http = 401)
end
it 'returns a token if authed' do
authorize 'admin', 's3cr3t'
post "#{prefix}/token"
expect(JSON.parse(last_response.body)['token'].length).to be(32)
expect_json(ok = true, http = 200)
end
end
end
end
describe '/token/:token' do
let(:redis) { double('redis') }
let(:prefix) { '/api/v1' }
before do
app.settings.set :config, config
app.settings.set :redis, redis
end
describe 'GET /token/:token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
get "#{prefix}/token/this"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
let(:config) { {
auth: true,
pools: [
{'name' => 'pool1', 'size' => 5}
]
} }
it 'returns a token' do
expect(redis).to receive(:hgetall).with('vmpooler__token__this').and_return({'user' => 'admin'})
expect(redis).to receive(:smembers).with('vmpooler__running__pool1').and_return(['vmhostname'])
expect(redis).to receive(:hget).with('vmpooler__vm__vmhostname', 'token:token').and_return('this')
get "#{prefix}/token/this"
expect(JSON.parse(last_response.body)['ok']).to eq(true)
expect(JSON.parse(last_response.body)['this']['user']).to eq('admin')
expect(JSON.parse(last_response.body)['this']['vms']['running']).to include('vmhostname')
expect_json(ok = true, http = 200)
end
end
end
describe 'DELETE /token/:token' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'returns a 404' do
delete "#{prefix}/token/this"
expect_json(ok = false, http = 404)
end
end
context '(auth configured)' do
before do
allow(redis).to receive(:del).and_return '1'
end
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
delete "#{prefix}/token/this"
expect_json(ok = false, http = 401)
end
it 'deletes a token if authed' do
authorize 'admin', 's3cr3t'
delete "#{prefix}/token/this"
expect_json(ok = true, http = 200)
end
end
end
end
describe '/vm' 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' do
it 'returns a single VM' do
post "#{prefix}/vm", '{"pool1":"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 '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 nonexistent 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
post "#{prefix}/vm", '{"pool1":"1","pool2":"1"}'
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
it 'returns multiple VMs even when multiple instances from the same pool are requested' do
post "#{prefix}/vm", '{"pool1":"2","pool2":"1"}'
expected = {
ok: true,
pool1: {
hostname: [ 'abcdefghijklmnop', 'abcdefghijklmnop' ]
},
pool2: {
hostname: 'qrstuvwxyz012345'
}
}
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = true, http = 200)
end
it 'returns multiple VMs even when multiple instances from multiple pools are requested' do
post "#{prefix}/vm", '{"pool1":"2","pool2":"3"}'
expected = {
ok: true,
pool1: {
hostname: [ 'abcdefghijklmnop', 'abcdefghijklmnop' ]
},
pool2: {
hostname: [ 'qrstuvwxyz012345', 'qrstuvwxyz012345', 'qrstuvwxyz012345' ]
}
}
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
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 'fails when not all requested vms can be allocated, when requesting multiple instances from a pool' do
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop").exactly(2).times
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 'fails when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop").exactly(2).times
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
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":"1"}', {
'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":"1"}', {
'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":"1"}'
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/: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 nonexistent 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
it 'returns multiple VMs even when multiple instances from the same pool are requested' do
post "#{prefix}/vm/pool1+pool1+pool2", ''
expected = {
ok: true,
pool1: {
hostname: [ 'abcdefghijklmnop', 'abcdefghijklmnop' ]
},
pool2: {
hostname: 'qrstuvwxyz012345'
}
}
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = true, http = 200)
end
it 'returns multiple VMs even when multiple instances from multiple pools are requested' do
post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", ''
expected = {
ok: true,
pool1: {
hostname: [ 'abcdefghijklmnop', 'abcdefghijklmnop' ]
},
pool2: {
hostname: [ 'qrstuvwxyz012345', 'qrstuvwxyz012345', 'qrstuvwxyz012345' ]
}
}
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
post "#{prefix}/vm/pool1+pool2", ''
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
post "#{prefix}/vm/pool1+pool2", ''
expected = { ok: false }
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
end
it 'fails when not all requested vms can be allocated, when requesting multiple instances from a pool' do
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
post "#{prefix}/vm/pool1+pool1+pool2", ''
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop").exactly(2).times
post "#{prefix}/vm/pool1+pool1+pool2", ''
expected = { ok: false }
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
end
it 'fails when not all requested vms can be allocated, when requesting multiple instances from multiple pools' do
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
allow(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop")
post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", ''
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
allow(redis).to receive(:spop).with('vmpooler__ready__pool1').and_return 'abcdefghijklmnop'
allow(redis).to receive(:spop).with('vmpooler__ready__pool2').and_return nil
expect(redis).to receive(:sadd).with("vmpooler__ready__pool1", "abcdefghijklmnop").exactly(2).times
post "#{prefix}/vm/pool1+pool1+pool2+pool2+pool2", ''
expected = { ok: false }
expect(last_response.body).to eq(JSON.pretty_generate(expected))
expect_json(ok = false, http = 503)
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
let(:redis) { double('redis') }
let(:prefix) { '/api/v1' }
let(:config) { {
pools: [
{'name' => 'pool1', 'size' => 5},
{'name' => 'pool2', 'size' => 10}
]
} }
before do
app.settings.set :config, config
app.settings.set :redis, redis
allow(redis).to receive(:exists).and_return '1'
allow(redis).to receive(:hset).and_return '1'
end
describe 'PUT /vm/:hostname' do
it 'allows tags to be set' do
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":"rspec"}}'
expect_json(ok = true, http = 200)
end
it 'skips empty tags' do
put "#{prefix}/vm/testhost", '{"tags":{"tested_by":""}}'
expect_json(ok = true, http = 200)
end
it 'does not set tags if request body format is invalid' do
put "#{prefix}/vm/testhost", '{"tags":{"tested"}}'
expect_json(ok = false, http = 400)
end
context '(allowed_tags configured)' do
let(:config) { {
config: {
'allowed_tags' => ['created_by', 'project', 'url']
}
} }
it 'fails if specified tag is not in allowed_tags array' do
put "#{prefix}/vm/testhost", '{"tags":{"created_by":"rspec","tested_by":"rspec"}}'
expect_json(ok = false, http = 400)
end
end
context '(tagfilter configured)' do
let(:config) { {
tagfilter: { 'url' => '(.*)\/' },
} }
it 'correctly filters tags' do
expect(redis).to receive(:hset).with("vmpooler__vm__testhost", "tag:url", "foo.com")
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com/something.html"}}'
expect_json(ok = true, http = 200)
end
it 'doesn\'t eat tags not matching filter' do
expect(redis).to receive(:hset).with("vmpooler__vm__testhost", "tag:url", "foo.com")
put "#{prefix}/vm/testhost", '{"tags":{"url":"foo.com"}}'
expect_json(ok = true, http = 200)
end
end
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'allows VM lifetime to be modified without a token' do
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = true, http = 200)
end
it 'does not allow a lifetime to be 0' do
put "#{prefix}/vm/testhost", '{"lifetime":"0"}'
expect_json(ok = false, http = 400)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'allows VM lifetime to be modified with a token' do
put "#{prefix}/vm/testhost", '{"lifetime":"1"}', {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 200)
end
it 'does not allows VM lifetime to be modified without a token' do
put "#{prefix}/vm/testhost", '{"lifetime":"1"}'
expect_json(ok = false, http = 401)
end
end
end
describe 'DELETE /vm/:hostname' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'does not delete a non-existant VM' do
expect(redis).to receive(:hgetall).and_return({})
expect(redis).not_to receive(:sadd)
expect(redis).not_to receive(:srem)
delete "#{prefix}/vm/testhost"
expect_json(ok = false, http = 404)
end
it 'deletes an existing VM' do
expect(redis).to receive(:hgetall).with('vmpooler__vm__testhost').and_return({"template" => "pool1"})
expect(redis).to receive(:srem).and_return(true)
expect(redis).to receive(:sadd)
delete "#{prefix}/vm/testhost"
expect_json(ok = true, http = 200)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
context '(checked-out without token)' do
it 'deletes a VM without supplying a token' do
expect(redis).to receive(:hgetall).with('vmpooler__vm__testhost').and_return({"template" => "pool1"})
expect(redis).to receive(:srem).and_return(true)
expect(redis).to receive(:sadd)
delete "#{prefix}/vm/testhost"
expect_json(ok = true, http = 200)
end
end
context '(checked-out with token)' do
it 'fails to delete a VM without supplying a token' do
expect(redis).to receive(:hgetall).with('vmpooler__vm__testhost').and_return({"template" => "pool1", "token:token" => "abcdefghijklmnopqrstuvwxyz012345"})
expect(redis).not_to receive(:sadd)
expect(redis).not_to receive(:srem)
delete "#{prefix}/vm/testhost"
expect_json(ok = false, http = 401)
end
it 'deletes a VM when token is supplied' do
expect(redis).to receive(:hgetall).with('vmpooler__vm__testhost').and_return({"template" => "pool1", "token:token" => "abcdefghijklmnopqrstuvwxyz012345"})
expect(redis).to receive(:srem).and_return(true)
expect(redis).to receive(:sadd)
delete "#{prefix}/vm/testhost", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 200)
end
end
end
end
describe 'POST /vm/:hostname/snapshot' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'creates a snapshot' do
expect(redis).to receive(:sadd)
post "#{prefix}/vm/testhost/snapshot"
expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32)
expect_json(ok = true, http = 202)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
post "#{prefix}/vm/testhost/snapshot"
expect_json(ok = false, http = 401)
end
it 'creates a snapshot if authed' do
expect(redis).to receive(:sadd)
post "#{prefix}/vm/testhost/snapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect(JSON.parse(last_response.body)['testhost']['snapshot'].length).to be(32)
expect_json(ok = true, http = 202)
end
end
end
describe 'POST /vm/:hostname/snapshot/:snapshot' do
context '(auth not configured)' do
let(:config) { { auth: false } }
it 'reverts to a snapshot' do
expect(redis).to receive(:hget).with('vmpooler__vm__testhost', 'snapshot:testsnapshot').and_return(1)
expect(redis).to receive(:sadd)
post "#{prefix}/vm/testhost/snapshot/testsnapshot"
expect_json(ok = true, http = 202)
end
end
context '(auth configured)' do
let(:config) { { auth: true } }
it 'returns a 401 if not authed' do
post "#{prefix}/vm/testhost/snapshot"
expect_json(ok = false, http = 401)
end
it 'reverts to a snapshot if authed' do
expect(redis).to receive(:hget).with('vmpooler__vm__testhost', 'snapshot:testsnapshot').and_return(1)
expect(redis).to receive(:sadd)
post "#{prefix}/vm/testhost/snapshot/testsnapshot", "", {
'HTTP_X_AUTH_TOKEN' => 'abcdefghijklmnopqrstuvwxyz012345'
}
expect_json(ok = true, http = 202)
end
end
end
end
end

View file

@ -10,6 +10,10 @@ describe Vmpooler::API do
describe 'Dashboard' do describe 'Dashboard' do
before(:each) do
redis.flushdb
end
context '/' do context '/' do
before { get '/' } before { get '/' }
@ -38,7 +42,6 @@ describe Vmpooler::API do
it { expect(last_response.status).to eq(404) } it { expect(last_response.status).to eq(404) }
it { expect(last_response.header['Content-Type']).to eq('application/json') } it { expect(last_response.header['Content-Type']).to eq('application/json') }
it { expect(last_response.body).to eq(JSON.pretty_generate({ok: false})) } it { expect(last_response.body).to eq(JSON.pretty_generate({ok: false})) }
end end
describe '/dashboard/stats/vmpooler/pool' do describe '/dashboard/stats/vmpooler/pool' do
@ -49,7 +52,6 @@ describe Vmpooler::API do
], ],
graphite: {} graphite: {}
} } } }
let(:redis) { double('redis') }
before do before do
$config = config $config = config
@ -59,13 +61,12 @@ describe Vmpooler::API do
end end
context 'without history param' do context 'without history param' do
it 'returns basic JSON' do it 'returns basic JSON' do
allow(redis).to receive(:scard) create_ready_vm('pool1', 'vm1')
allow(redis).to receive(:scard).with('vmpooler__ready__pool1').and_return(3) create_ready_vm('pool1', 'vm2')
allow(redis).to receive(:scard).with('vmpooler__ready__pool2').and_return(2) create_ready_vm('pool1', 'vm3')
create_ready_vm('pool2', 'vm4')
expect(redis).to receive(:scard).twice create_ready_vm('pool2', 'vm5')
get '/dashboard/stats/vmpooler/pool' get '/dashboard/stats/vmpooler/pool'
@ -78,19 +79,15 @@ describe Vmpooler::API do
expect(last_response.body).to eq(JSON.pretty_generate(json_hash)) expect(last_response.body).to eq(JSON.pretty_generate(json_hash))
expect(last_response.header['Content-Type']).to eq('application/json') expect(last_response.header['Content-Type']).to eq('application/json')
end end
end end
context 'with history param' do context 'with history param' do
it 'returns JSON with null history when redis does not has values' do it 'returns JSON with zeroed history when redis does not have values' do
allow(redis).to receive(:scard)
expect(redis).to receive(:scard).exactly(4).times
get '/dashboard/stats/vmpooler/pool', :history => true get '/dashboard/stats/vmpooler/pool', :history => true
json_hash = { json_hash = {
pool1: {size: 5, ready: nil, history: [nil]}, pool1: {size: 5, ready: 0, history: [0]},
pool2: {size: 1, ready: nil, history: [nil]} pool2: {size: 1, ready: 0, history: [0]}
} }
expect(last_response).to be_ok expect(last_response).to be_ok
@ -99,10 +96,11 @@ describe Vmpooler::API do
end end
it 'returns JSON with history when redis has values' do it 'returns JSON with history when redis has values' do
allow(redis).to receive(:scard).with('vmpooler__ready__pool1').and_return(3) create_ready_vm('pool1', 'vm1')
allow(redis).to receive(:scard).with('vmpooler__ready__pool2').and_return(2) create_ready_vm('pool1', 'vm2')
create_ready_vm('pool1', 'vm3')
expect(redis).to receive(:scard).exactly(4).times create_ready_vm('pool2', 'vm4')
create_ready_vm('pool2', 'vm5')
get '/dashboard/stats/vmpooler/pool', :history => true get '/dashboard/stats/vmpooler/pool', :history => true
@ -115,9 +113,7 @@ describe Vmpooler::API do
expect(last_response.body).to eq(JSON.pretty_generate(json_hash)) expect(last_response.body).to eq(JSON.pretty_generate(json_hash))
expect(last_response.header['Content-Type']).to eq('application/json') expect(last_response.header['Content-Type']).to eq('application/json')
end end
end end
end end
describe '/dashboard/stats/vmpooler/running' do describe '/dashboard/stats/vmpooler/running' do
@ -129,7 +125,6 @@ describe Vmpooler::API do
], ],
graphite: {} graphite: {}
} } } }
let(:redis) { double('redis') }
before do before do
$config = config $config = config
@ -141,10 +136,6 @@ describe Vmpooler::API do
context 'without history param' do context 'without history param' do
it 'returns basic JSON' do it 'returns basic JSON' do
allow(redis).to receive(:scard)
expect(redis).to receive(:scard).exactly(3).times
get '/dashboard/stats/vmpooler/running' get '/dashboard/stats/vmpooler/running'
json_hash = {pool: {running: 0}, diffpool: {running: 0}} json_hash = {pool: {running: 0}, diffpool: {running: 0}}
@ -155,9 +146,18 @@ describe Vmpooler::API do
end end
it 'adds major correctly' do it 'adds major correctly' do
allow(redis).to receive(:scard).with('vmpooler__running__pool-1').and_return(3) create_running_vm('pool-1', 'vm1')
allow(redis).to receive(:scard).with('vmpooler__running__pool-2').and_return(5) create_running_vm('pool-1', 'vm2')
allow(redis).to receive(:scard).with('vmpooler__running__diffpool-1').and_return(2) create_running_vm('pool-1', 'vm3')
create_running_vm('pool-2', 'vm4')
create_running_vm('pool-2', 'vm5')
create_running_vm('pool-2', 'vm6')
create_running_vm('pool-2', 'vm7')
create_running_vm('pool-2', 'vm8')
create_running_vm('diffpool-1', 'vm9')
create_running_vm('diffpool-1', 'vm10')
get '/dashboard/stats/vmpooler/running' get '/dashboard/stats/vmpooler/running'
@ -167,10 +167,7 @@ describe Vmpooler::API do
expect(last_response.body).to eq(JSON.pretty_generate(json_hash)) expect(last_response.body).to eq(JSON.pretty_generate(json_hash))
expect(last_response.header['Content-Type']).to eq('application/json') expect(last_response.header['Content-Type']).to eq('application/json')
end end
end end
end end
end end
end end