Skip to content

Commit

Permalink
Merge pull request #162 from puppetlabs/per-pool-stats-in-status
Browse files Browse the repository at this point in the history
[QENG-4181] Add per-pool stats to `/status` API
  • Loading branch information
mckern authored Sep 23, 2016
2 parents 1bf0af2 + 9b44c2f commit 8286ec2
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 3 deletions.
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ rvm:
- 2.2.1
- 2.2.2
- jruby-1.7.8
# This below is a temporary shim to bypass these bundler+jruby bugs:
#
# /~https://github.com/bundler/bundler/issues/4975
# /~https://github.com/bundler/bundler/issues/4984
install:
- gem install bundler --version 1.12.5
- bundle _1.12.5_ install --jobs=3 --retry=3 --path=${BUNDLE_PATH:-vendor/bundle}
script:
- bundle _1.12.5_ exec rake
73 changes: 72 additions & 1 deletion lib/vmpooler/api/v1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,67 @@ def atomically_allocate_vms(payload)
result
end

# Provide run-time statistics
#
# Example:
#
# {
# "boot": {
# "duration": {
# "average": 163.6,
# "min": 65.49,
# "max": 830.07,
# "total": 247744.71000000002
# },
# "count": {
# "total": 1514
# }
# },
# "capacity": {
# "current": 968,
# "total": 975,
# "percent": 99.3
# },
# "clone": {
# "duration": {
# "average": 17.0,
# "min": 4.66,
# "max": 637.96,
# "total": 25634.15
# },
# "count": {
# "total": 1507
# }
# },
# "queue": {
# "pending": 12,
# "cloning": 0,
# "booting": 12,
# "ready": 968,
# "running": 367,
# "completed": 0,
# "total": 1347
# },
# "pools": {
# "ready": 100,
# "running": 120,
# "pending": 5,
# "max": 250,
# }
# "status": {
# "ok": true,
# "message": "Battle station fully armed and operational.",
# "empty": [ # NOTE: would not have 'ok: true' w/ "empty" pools
# "redhat-7-x86_64",
# "ubuntu-1404-i386"
# ],
# "uptime": 179585.9
# }
get "#{api_prefix}/status/?" do
content_type :json

result = {
pools: {},
status: {
ok: true,
message: 'Battle station fully armed and operational.'
Expand All @@ -136,7 +193,21 @@ def atomically_allocate_vms(payload)

# Check for empty pools
pools.each do |pool|
if backend.scard('vmpooler__ready__' + pool['name']).to_i == 0
# REMIND: move this out of the API and into the back-end
ready = backend.scard('vmpooler__ready__' + pool['name']).to_i
running = backend.scard('vmpooler__running__' + pool['name']).to_i
pending = backend.scard('vmpooler__pending__' + pool['name']).to_i
max = pool['size']

result[:pools][pool['name']] = {
ready: ready,
running: running,
pending: pending,
max: max
}

# for backwards compatibility, include separate "empty" stats in "status" block
if ready == 0
result[:status][:empty] ||= []
result[:status][:empty].push(pool['name'])

Expand Down
9 changes: 7 additions & 2 deletions spec/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ def token_exists?(token)
def create_ready_vm(template, name, token = nil)
create_vm(name, token)
redis.sadd("vmpooler__ready__#{template}", name)
# REMIND: should be __vm__?
redis.hset("vmpooler_vm_#{name}", "template", template)
redis.hset("vmpooler__vm__#{name}", "template", template)
end

def create_running_vm(template, name, token = nil)
Expand All @@ -45,6 +44,12 @@ def create_running_vm(template, name, token = nil)
redis.hset("vmpooler__vm__#{name}", "template", template)
end

def create_pending_vm(template, name, token = nil)
create_vm(name, token)
redis.sadd("vmpooler__pending__#{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
Expand Down
111 changes: 111 additions & 0 deletions spec/vmpooler/api/v1/status_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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 '/status' 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 'GET /status' do
it 'returns the configured maximum size for each pool' do
get "#{prefix}/status/"

# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["max"]).to be(5)
expect(result["pools"]["pool2"]["max"]).to be(10)
end

it 'returns the number of ready vms for each pool' do
3.times {|i| create_ready_vm("pool1", "vm-#{i}") }
get "#{prefix}/status/"

# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["ready"]).to be(3)
expect(result["pools"]["pool2"]["ready"]).to be(0)
end

it 'returns the number of running vms for each pool' do
3.times {|i| create_running_vm("pool1", "vm-#{i}") }
4.times {|i| create_running_vm("pool2", "vm-#{i}") }

get "#{prefix}/status/"

# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["running"]).to be(3)
expect(result["pools"]["pool2"]["running"]).to be(4)
end

it 'returns the number of pending vms for each pool' do
3.times {|i| create_pending_vm("pool1", "vm-#{i}") }
4.times {|i| create_pending_vm("pool2", "vm-#{i}") }

get "#{prefix}/status/"

# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["pools"]["pool1"]["pending"]).to be(3)
expect(result["pools"]["pool2"]["pending"]).to be(4)
end

it '(for v1 backwards compatibility) lists any empty pools in the status section' do
get "#{prefix}/status/"

# of course /status doesn't conform to the weird standard everything else uses...
expect(last_response.header['Content-Type']).to eq('application/json')
result = JSON.parse(last_response.body)
expect(result["status"]["empty"].sort).to eq(["pool1", "pool2"])
end
end
end
end

0 comments on commit 8286ec2

Please sign in to comment.