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
sudo: false
language: ruby
services:
- redis-server
rvm:
- 1.9.3
- 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
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

View file

@ -457,6 +457,15 @@ module Vmpooler
result[params[:hostname]]['disk'] = rdata['disk'].split(':')
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']
result[params[:hostname]]['domain'] = config['domain']
end

View file

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